(function (factory) {
  // 引用 css —— webapck
  //require('wangeditor/dist/css/wangEditor.css');
  module.exports = factory(
    // 传入 jquery ，支持使用 npm 方式或者自己定义jquery的路径
    require('jquery')
  );
})(function ($) {

  // 验证是否引用jquery
  if (!$ || !$.fn || !$.fn.jquery) {
    alert('在引用wangEditor.js之前，先引用jQuery，否则无法使用 wangEditor');
    return;
  }

  let WangEditor = null;
  // 定义扩展函数
  let _e = function (fn) {
    if (WangEditor) {
      // 执行传入的函数
      fn(WangEditor, $);
    }
  };
// 定义构造函数
  (function (window, $) {

    // 编辑器（整体）构造函数
    let E = function (elem) {
      // 支持 id 和 element 两种形式
      if (typeof elem === 'string') {
        elem = '#' + elem;
      }

      // ---------------获取基本节点------------------
      let $elem = $(elem);
      if ($elem.length !== 1) {
        return;
      }
      let nodeName = $elem[0].nodeName;
      if (nodeName !== 'TEXTAREA' && nodeName !== 'DIV') {
        // 只能是 textarea 和 div ，其他类型的元素不行
        return;
      }
      this.valueNodeName = nodeName.toLowerCase();
      this.$valueContainer = $elem;

      // 记录 elem 的 prev 和 parent（最后渲染 editor 要用到）
      this.$prev = $elem.prev();
      this.$parent = $elem.parent();

      // ------------------初始化------------------
      this.init();
    };

    E.fn = E.prototype;

    E.$body = $('body');
    E.$document = $(document);
    E.$window = $(window);
    E.userAgent = navigator.userAgent;
    E.getComputedStyle = window.getComputedStyle;
    E.w3cRange = typeof document.createRange === 'function';
    E.hostname = location.hostname.toLowerCase();
    E.websiteHost = 'wangeditor.github.io|www.wangeditor.com|wangeditor.coding.me';
    E.isOnWebsite = E.websiteHost.indexOf(E.hostname) >= 0;
    E.docsite = 'http://www.kancloud.cn/wangfupeng/wangeditor2/113961';

    //二次开发全屏功能
    E.fullScreenClick = null;
    E.fullScreenClickSelected = null;


    // 暴露给全局对象
    WangEditor = E;

    // 注册 plugin 事件，用于用户自定义插件
    // 用户在引用 WangEditor.js 之后，还可以通过 E.plugin() 注入自定义函数，
    // 该函数将会在 editor.create() 方法的最后一步执行
    E.plugin = function (fn) {
      if (!E._plugins) {
        E._plugins = [];
      }

      if (typeof fn === 'function') {
        E._plugins.push(fn);
      }
    };

  })(window, $);
// editor 绑定事件
  _e(function (E, $) {

    E.fn.init = function () {

      // 初始化 editor 默认配置
      this.initDefaultConfig();

      // 增加container
      this.addEditorContainer();

      // 增加编辑区域
      this.addTxt();

      // 增加menuContainer
      this.addMenuContainer();

      // 初始化菜单集合
      this.menus = {};

      // 初始化commandHooks
      this.commandHooks();

    };

  });
// editor api
  _e(function (E, $) {

    // 预定义 ready 事件
    E.fn.ready = function (fn) {

      if (!this.readyFns) {
        this.readyFns = [];
      }

      this.readyFns.push(fn);
    };

    // 处理ready事件
    E.fn.readyHeadler = function () {
      let fns = this.readyFns;

      while (fns.length) {
        fns.shift().call(this);
      }
    };

    // 更新内容到 $valueContainer
    E.fn.updateValue = function () {
      let editor = this;
      let $valueContainer = editor.$valueContainer;
      let $txt = editor.txt.$txt;

      if ($valueContainer === $txt) {
        // 传入生成编辑器的div，即是编辑区域
        return;
      }

      let value = $txt.html();
      $valueContainer.val(value);
    };

    // 获取初始化的内容
    E.fn.getInitValue = function () {
      let editor = this;
      let $valueContainer = editor.$valueContainer;
      let currentValue = '';
      let nodeName = editor.valueNodeName;
      if (nodeName === 'div') {
        currentValue = $valueContainer.html();
      } else if (nodeName === 'textarea') {
        currentValue = $valueContainer.val();
      }

      return currentValue;
    };

    // 触发菜单updatestyle
    E.fn.updateMenuStyle = function () {
      let menus = this.menus;

      $.each(menus, function (k, menu) {
        menu.updateSelected();
      });
    };

    // 除了传入的 menuIds，其他全部启用
    E.fn.enableMenusExcept = function (menuIds) {
      if (this._disabled) {
        // 编辑器处于禁用状态，则不执行改操作
        return;
      }
      // menuIds参数：支持数组和字符串
      menuIds = menuIds || [];
      if (typeof menuIds === 'string') {
        menuIds = [menuIds];
      }

      $.each(this.menus, function (k, menu) {
        if (menuIds.indexOf(k) >= 0) {
          return;
        }
        menu.disabled(false);
      });
    };

    // 除了传入的 menuIds，其他全部禁用
    E.fn.disableMenusExcept = function (menuIds) {
      if (this._disabled) {
        // 编辑器处于禁用状态，则不执行改操作
        return;
      }
      // menuIds参数：支持数组和字符串
      menuIds = menuIds || [];
      if (typeof menuIds === 'string') {
        menuIds = [menuIds];
      }

      $.each(this.menus, function (k, menu) {
        if (menuIds.indexOf(k) >= 0) {
          return;
        }
        menu.disabled(true);
      });
    };

    // 隐藏所有 dropPanel droplist modal
    E.fn.hideDropPanelAndModal = function () {
      let menus = this.menus;

      $.each(menus, function (k, menu) {
        let m = menu.dropPanel || menu.dropList || menu.modal;
        if (m && m.hide) {
          m.hide();
        }
      });
    };

  });
// selection range API
  _e(function (E, $) {

    // 用到 w3c range 的函数，如果检测到浏览器不支持 w3c range，则赋值为空函数
    let ieRange = !E.w3cRange;

    function emptyFn() {
    }

    // 设置或读取当前的range
    E.fn.currentRange = function (cr) {
      if (cr) {
        this._rangeData = cr;
      } else {
        return this._rangeData;
      }
    };

    // 将当前选区折叠
    E.fn.collapseRange = function (range, opt) {
      // opt 参数说明：'start'-折叠到开始; 'end'-折叠到结束
      opt = opt || 'end';
      opt = opt === 'start' ? true : false;

      range = range || this.currentRange();

      if (range) {
        // 合并，保存
        range.collapse(opt);
        this.currentRange(range);
      }
    };

    // 获取选区的文字
    E.fn.getRangeText = ieRange ? emptyFn : function (range) {
      range = range || this.currentRange();
      if (!range) {
        return;
      }
      return range.toString();
    };

    // 获取选区对应的DOM对象
    E.fn.getRangeElem = ieRange ? emptyFn : function (range) {
      range = range || this.currentRange();
      let dom = range.commonAncestorContainer;

      if (dom.nodeType === 1) {
        return dom;
      } else {
        return dom.parentNode;
      }
    };

    // 选区内容是否为空？
    E.fn.isRangeEmpty = ieRange ? emptyFn : function (range) {
      range = range || this.currentRange();

      if (range && range.startContainer) {
        if (range.startContainer === range.endContainer) {
          if (range.startOffset === range.endOffset) {
            return true;
          }
        }
      }

      return false;
    };

    // 保存选区数据
    E.fn.saveSelection = ieRange ? emptyFn : function (range) {
      let self = this,
        _parentElem,
        selection,
        txt = self.txt.$txt.get(0);

      if (range) {
        _parentElem = range.commonAncestorContainer;
      } else {
        selection = document.getSelection();
        if (selection.getRangeAt && selection.rangeCount) {
          range = document.getSelection().getRangeAt(0);
          _parentElem = range.commonAncestorContainer;
        }
      }
      // 确定父元素一定要包含在编辑器区域内
      if (_parentElem && ($.contains(txt, _parentElem) || txt === _parentElem)) {
        // 保存选择区域
        self.currentRange(range);
      }
    };

    // 恢复选中区域
    E.fn.restoreSelection = ieRange ? emptyFn : function (range) {
      let selection;

      range = range || this.currentRange();

      if (!range) {
        return;
      }

      // 使用 try catch 来防止 IE 某些情况报错
      try {
        selection = document.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
      } catch (ex) {
        E.error('执行 editor.restoreSelection 时，IE可能会有异常，不影响使用');
      }
    };

    // 根据elem恢复选区
    E.fn.restoreSelectionByElem = ieRange ? emptyFn : function (elem, opt) {
      // opt参数说明：'start'-折叠到开始，'end'-折叠到结束，'all'-全部选中
      if (!elem) {
        return;
      }
      opt = opt || 'end'; // 默认为折叠到结束

      // 根据elem获取选区
      this.setRangeByElem(elem);

      // 根据 opt 折叠选区
      if (opt === 'start') {
        this.collapseRange(this.currentRange(), 'start');
      }
      if (opt === 'end') {
        this.collapseRange(this.currentRange(), 'end');
      }

      // 恢复选区
      this.restoreSelection();
    };

    // 初始化选区
    E.fn.initSelection = ieRange ? emptyFn : function () {
      let editor = this;
      if (editor.currentRange()) {
        //如果currentRange有值，则不用再初始化
        return;
      }

      let range;
      let $txt = editor.txt.$txt;
      let $firstChild = $txt.children().first();

      if ($firstChild.length) {
        editor.restoreSelectionByElem($firstChild.get(0));
      }
    };

    // 根据元素创建选区
    E.fn.setRangeByElem = ieRange ? emptyFn : function (elem) {
      let editor = this;
      let txtElem = editor.txt.$txt.get(0);
      if (!elem || !$.contains(txtElem, elem)) {
        return;
      }

      // 找到elem的第一个 textNode 和 最后一个 textNode
      let firstTextNode = elem.firstChild;
      while (firstTextNode) {
        if (firstTextNode.nodeType === 3) {
          break;
        }
        // 继续向下
        firstTextNode = firstTextNode.firstChild;
      }
      let lastTextNode = elem.lastChild;
      while (lastTextNode) {
        if (lastTextNode.nodeType === 3) {
          break;
        }
        // 继续向下
        lastTextNode = lastTextNode.lastChild;
      }

      let range = document.createRange();
      if (firstTextNode && lastTextNode) {
        // 说明 elem 有内容，能取到子元素
        range.setStart(firstTextNode, 0);
        range.setEnd(lastTextNode, lastTextNode.textContent.length);
      } else {
        // 说明 elem 无内容
        range.setStart(elem, 0);
        range.setEnd(elem, 0);
      }

      // 保存选区
      editor.saveSelection(range);
    };

  });
// selection range API - IE8及以下
  _e(function (E, $) {

    if (E.w3cRange) {
      // 说明支持 W3C 的range方法
      return;
    }

    // -----------------IE8时，需要重写以下方法-------------------

    // 获取选区的文字
    E.fn.getRangeText = function (range) {
      range = range || this.currentRange();
      if (!range) {
        return;
      }
      return range.text;
    };

    // 获取选区对应的DOM对象
    E.fn.getRangeElem = function (range) {
      range = range || this.currentRange();
      if (!range) {
        return;
      }
      let dom = range.parentElement();

      if (dom.nodeType === 1) {
        return dom;
      } else {
        return dom.parentNode;
      }
    };

    // 选区内容是否为空？
    E.fn.isRangeEmpty = function (range) {
      range = range || this.currentRange();

      if (range && range.text) {
        return false;
      }

      return true;
    };

    // 保存选区数据
    E.fn.saveSelection = function (range) {
      let self = this,
        _parentElem,
        selection,
        txt = self.txt.$txt.get(0);

      if (range) {
        _parentElem = range.parentElement();
      } else {
        range = document.selection.createRange();
        if (typeof range.parentElement === 'undefined') {
          //IE6、7中，insertImage后会执行此处
          //由于找不到range.parentElement，所以干脆将_parentElem赋值为null
          _parentElem = null;
        } else {
          _parentElem = range.parentElement();
        }
      }

      // 确定父元素一定要包含在编辑器区域内
      if (_parentElem && ($.contains(txt, _parentElem) || txt === _parentElem)) {
        // 保存选择区域
        self.currentRange(range);
      }
    };

    // 恢复选中区域
    E.fn.restoreSelection = function (currentRange) {
      let editor = this,
        selection,
        range;

      currentRange = currentRange || editor.currentRange();
      if (!currentRange) {
        return;
      }

      range = document.selection.createRange();
      try {
        // 此处，plupload上传上传图片时，IE8-会报一个『参数无效』的错误
        range.setEndPoint('EndToEnd', currentRange);
      } catch (ex) {

      }

      if (currentRange.text.length === 0) {
        try {
          // IE8 插入表情会报错
          range.collapse(false);
        } catch (ex) {

        }

      } else {
        range.setEndPoint('StartToStart', currentRange);
      }
      range.select();
    };

  });
// editor command hooks
  _e(function (E, $) {

    E.fn.commandHooks = function () {
      let editor = this;
      let commandHooks = {};

      // insertHtml
      commandHooks.insertHtml = function (html) {
        let $elem = $(html);
        let rangeElem = editor.getRangeElem();
        let targetElem;

        targetElem = editor.getLegalTags(rangeElem);
        if (!targetElem) {
          return;
        }

        $(targetElem).after($elem);
      };

      // 保存到对象
      editor.commandHooks = commandHooks;
    };

  });
// editor command API
  _e(function (E, $) {

    // 基本命令
    E.fn.command = function (e, commandName, commandValue, callback) {
      let editor = this;
      let hooks;

      function commandFn() {
        if (!commandName) {
          return;
        }
        if (editor.queryCommandSupported(commandName)) {
          // 默认命令
          document.execCommand(commandName, false, commandValue);
        } else {
          // hooks 命令
          hooks = editor.commandHooks;
          if (commandName in hooks) {
            hooks[commandName](commandValue);
          }
        }
      }

      this.customCommand(e, commandFn, callback);
    };

    // 针对一个elem对象执行基础命令
    E.fn.commandForElem = function (elemOpt, e, commandName, commandValue, callback) {
      // 取得查询elem的查询条件和验证函数
      let selector;
      let check;
      if (typeof elemOpt === 'string') {
        selector = elemOpt;
      } else {
        selector = elemOpt.selector;
        check = elemOpt.check;
      }

      // 查询elem
      let rangeElem = this.getRangeElem();
      rangeElem = this.getSelfOrParentByName(rangeElem, selector, check);

      // 根据elem设置range
      if (rangeElem) {
        this.setRangeByElem(rangeElem);
      }

      // 然后执行基础命令
      this.command(e, commandName, commandValue, callback);
    };

    // 自定义命令
    E.fn.customCommand = function (e, commandFn, callback) {
      let editor = this;
      let range = editor.currentRange();

      if (!range) {
        // 目前没有选区，则无法执行命令
        e && e.preventDefault();
        return;
      }
      // 记录内容，以便撤销（执行命令之前就要记录）
      editor.undoRecord();

      // 恢复选区（有 range 参数）
      this.restoreSelection(range);

      // 执行命令事件
      commandFn.call(editor);

      // 保存选区（无参数，要从浏览器直接获取range信息）
      this.saveSelection();
      // 重新恢复选区（无参数，要取得刚刚从浏览器得到的range信息）
      this.restoreSelection();

      // 执行 callback
      if (callback && typeof callback === 'function') {
        callback.call(editor);
      }

      // 最后插入空行
      editor.txt.insertEmptyP();

      // 包裹暴露的img和text
      editor.txt.wrapImgAndText();

      // 更新内容
      editor.updateValue();

      // 更新菜单样式
      editor.updateMenuStyle();

      // 隐藏 dropPanel dropList modal  设置 200ms 间隔
      function hidePanelAndModal() {
        editor.hideDropPanelAndModal();
      }

      setTimeout(hidePanelAndModal, 200);

      if (e) {
        e.preventDefault();
      }
    };

    // 封装 document.queryCommandValue 函数
    // IE8 直接执行偶尔会报错，因此直接用 try catch 封装一下
    E.fn.queryCommandValue = function (commandName) {
      let result = '';
      try {
        result = document.queryCommandValue(commandName);
      } catch (ex) {

      }
      return result;
    };

    // 封装 document.queryCommandState 函数
    // IE8 直接执行偶尔会报错，因此直接用 try catch 封装一下
    E.fn.queryCommandState = function (commandName) {
      let result = false;
      try {
        result = document.queryCommandState(commandName);
      } catch (ex) {

      }
      return result;
    };

    // 封装 document.queryCommandSupported 函数
    E.fn.queryCommandSupported = function (commandName) {
      let result = false;
      try {
        result = document.queryCommandSupported(commandName);
      } catch (ex) {

      }
      return result;
    };

  });
// dom selector
  _e(function (E, $) {

    let matchesSelector;

    // matchesSelector hook
    function _matchesSelectorForIE(selector) {
      let elem = this;
      let $elems = $(selector);
      let result = false;

      // 用jquery查找 selector 所有对象，如果其中有一个和传入 elem 相同，则证明 elem 符合 selector
      $elems.each(function () {
        if (this === elem) {
          result = true;
          return false;
        }
      });

      return result;
    }

    // 从当前的elem，往上去查找合法标签 如 p head table blockquote ul ol 等
    E.fn.getLegalTags = function (elem) {
      let legalTags = this.config.legalTags;
      if (!legalTags) {
        E.error('配置项中缺少 legalTags 的配置');
        return;
      }
      return this.getSelfOrParentByName(elem, legalTags);
    };

    // 根据条件，查询自身或者父元素，符合即返回
    E.fn.getSelfOrParentByName = function (elem, selector, check) {

      if (!elem || !selector) {
        return;
      }

      if (!matchesSelector) {
        // 定义 matchesSelector 函数
        matchesSelector = elem.webkitMatchesSelector ||
          elem.mozMatchesSelector ||
          elem.oMatchesSelector ||
          elem.matchesSelector;
      }
      if (!matchesSelector) {
        // 如果浏览器本身不支持 matchesSelector 则使用自定义的hook
        matchesSelector = _matchesSelectorForIE;
      }

      let txt = this.txt.$txt.get(0);

      while (elem && txt !== elem && $.contains(txt, elem)) {
        if (matchesSelector.call(elem, selector)) {
          // 符合 selector 查询条件

          if (!check) {
            // 没有 check 验证函数，直接返回即可
            return elem;
          }

          if (check(elem)) {
            // 如果有 check 验证函数，还需 check 函数的确认
            return elem;
          }
        }

        // 如果上一步没经过验证，则将跳转到父元素
        elem = elem.parentNode;
      }

      return;
    };

  });
// undo redo
  _e(function (E, $) {

    let length = 20;  // 缓存的最大长度
    function _getRedoList(editor) {
      if (editor._redoList == null) {
        editor._redoList = [];
      }
      return editor._redoList;
    }

    function _getUndoList(editor) {
      if (editor._undoList == null) {
        editor._undoList = [];
      }
      return editor._undoList;
    }

    // 数据处理
    function _handle(editor, data, type) {
      // let range = data.range;
      // let range2 = range.cloneRange && range.cloneRange();
      let val = data.val;
      let html = editor.txt.$txt.html();

      if (val == null) {
        return;
      }

      if (val === html) {
        if (type === 'redo') {
          editor.redo();
          return;
        } else if (type === 'undo') {
          editor.undo();
          return;
        } else {
          return;
        }
      }

      // 保存数据
      editor.txt.$txt.html(val);
      // 更新数据到textarea（有必要的话）
      editor.updateValue();

      // onchange 事件
      if (editor.onchange && typeof editor.onchange === 'function') {
        editor.onchange.call(editor);
      }

      // ?????
      // 注释：$txt 被重新赋值之后，range会被重置，cloneRange() 也不好使
      // // 重置选区
      // if (range2) {
      //     editor.restoreSelection(range2);
      // }
    }

    // 记录
    E.fn.undoRecord = function () {
      let editor = this;
      let $txt = editor.txt.$txt;
      let val = $txt.html();
      let undoList = _getUndoList(editor);
      let redoList = _getRedoList(editor);
      let currentVal = undoList.length ? undoList[0] : '';

      if (val === currentVal.val) {
        return;
      }

      // 清空 redolist
      if (redoList.length) {
        redoList = [];
      }

      // 添加数据到 undoList
      undoList.unshift({
        range: editor.currentRange(),  // 将当前的range也记录下
        val: val
      });

      // 限制 undoList 长度
      if (undoList.length > length) {
        undoList.pop();
      }
    };

    // undo 操作
    E.fn.undo = function () {
      let editor = this;
      let undoList = _getUndoList(editor);
      let redoList = _getRedoList(editor);

      if (!undoList.length) {
        return;
      }

      // 取出 undolist 第一个值，加入 redolist
      let data = undoList.shift();
      redoList.unshift(data);

      // 并修改编辑器的内容
      _handle(this, data, 'undo');
    };

    // redo 操作
    E.fn.redo = function () {
      let editor = this;
      let undoList = _getUndoList(editor);
      let redoList = _getRedoList(editor);
      if (!redoList.length) {
        return;
      }

      // 取出 redolist 第一个值，加入 undolist
      let data = redoList.shift();
      undoList.unshift(data);

      // 并修改编辑器的内容
      _handle(this, data, 'redo');
    };
  });
// 暴露给用户的 API
  _e(function (E, $) {

    // 创建编辑器
    E.fn.create = function () {
      let editor = this;

      // 检查 E.$body 是否有值
      // 如果在 body 之前引用了 js 文件，body 尚未加载，可能没有值
      if (!E.$body || E.$body.length === 0) {
        E.$body = $('body');
        E.$document = $(document);
        E.$window = $(window);
      }

      // 执行 addMenus 之前：
      // 1. 允许用户修改 editor.UI 自定义配置UI
      // 2. 允许用户通过修改 editor.menus 来自定义配置菜单
      // 因此要在 create 时执行，而不是 init
      editor.addMenus();

      // 渲染
      editor.renderMenus();
      editor.renderMenuContainer();
      editor.renderTxt();
      editor.renderEditorContainer();

      // 绑定事件
      editor.eventMenus();
      editor.eventMenuContainer();
      editor.eventTxt();

      // 处理ready事件
      editor.readyHeadler();

      // 初始化选区
      editor.initSelection();

      // $txt 快捷方式
      editor.$txt = editor.txt.$txt;

      // 执行用户自定义事件，通过 E.ready() 添加
      let _plugins = E._plugins;
      if (_plugins && _plugins.length) {
        $.each(_plugins, function (k, val) {
          val.call(editor);
        });
      }
    };

    // 禁用编辑器
    E.fn.disable = function () {
      this.txt.$txt.removeAttr('contenteditable');
      this.disableMenusExcept();

      // 先禁用，再记录状态
      this._disabled = true;
    };
    // 启用编辑器
    E.fn.enable = function () {
      // 先解除状态记录，再启用
      this._disabled = false;
      this.txt.$txt.attr('contenteditable', 'true');
      this.enableMenusExcept();
    };

    // 销毁编辑器
    E.fn.destroy = function () {
      let self = this;
      let $valueContainer = self.$valueContainer;
      let $editorContainer = self.$editorContainer;
      let valueNodeName = self.valueNodeName;

      if (valueNodeName === 'div') {
        // div 生成的编辑器
        $valueContainer.removeAttr('contenteditable');
        $editorContainer.after($valueContainer);
        $editorContainer.hide();
      } else {
        // textarea 生成的编辑器
        $valueContainer.show();
        $editorContainer.hide();
      }
    };

    // 撤销 销毁编辑器
    E.fn.undestroy = function () {
      let self = this;
      let $valueContainer = self.$valueContainer;
      let $editorContainer = self.$editorContainer;
      let $menuContainer = self.menuContainer.$menuContainer;
      let valueNodeName = self.valueNodeName;

      if (valueNodeName === 'div') {
        // div 生成的编辑器
        $valueContainer.attr('contenteditable', 'true');
        $menuContainer.after($valueContainer);
        $editorContainer.show();
      } else {
        // textarea 生成的编辑器
        $valueContainer.hide();
        $editorContainer.show();
      }
    };

    // 清空内容的快捷方式
    E.fn.clear = function () {
      let editor = this;
      let $txt = editor.txt.$txt;
      $txt.html('<p><br></p>');
      editor.restoreSelectionByElem($txt.find('p').get(0));
    };

  });
// menuContainer 构造函数
  _e(function (E, $) {

    // 定义构造函数
    let MenuContainer = function (editor) {
      this.editor = editor;
      this.init();
    };

    MenuContainer.fn = MenuContainer.prototype;

    // 暴露给 E
    E.MenuContainer = MenuContainer;

  });
// MenuContainer.fn bind fn
  _e(function (E, $) {

    let MenuContainer = E.MenuContainer;

    // 初始化
    MenuContainer.fn.init = function () {
      let self = this;
      let $menuContainer = $('<div class="wangEditor-menu-container clearfix"></div>');

      self.$menuContainer = $menuContainer;

      // change shadow
      self.changeShadow();
    };

    // 编辑区域滚动时，增加shadow
    MenuContainer.fn.changeShadow = function () {
      let $menuContainer = this.$menuContainer;
      let editor = this.editor;
      let $txt = editor.txt.$txt;

      $txt.on('scroll', function () {
        if ($txt.scrollTop() > 10) {
          $menuContainer.addClass('wangEditor-menu-shadow');
        } else {
          $menuContainer.removeClass('wangEditor-menu-shadow');
        }
      });
    };

  });
// MenuContainer.fn API
  _e(function (E, $) {

    let MenuContainer = E.MenuContainer;

    MenuContainer.fn.render = function () {
      let $menuContainer = this.$menuContainer;
      let $editorContainer = this.editor.$editorContainer;

      $editorContainer.append($menuContainer);
    };

    // 获取菜单栏的高度
    MenuContainer.fn.height = function () {
      let $menuContainer = this.$menuContainer;
      return $menuContainer.height();
    };

    // 添加菜单
    MenuContainer.fn.appendMenu = function (groupIdx, menu) {
      // 判断是否需要新增一个菜单组
      this._addGroup(groupIdx);
      // 增加菜单（返回 $menuItem）
      return this._addOneMenu(menu);
    };
    MenuContainer.fn._addGroup = function (groupIdx) {
      let $menuContainer = this.$menuContainer;
      let $menuGroup;
      if (!this.$currentGroup || this.currentGroupIdx !== groupIdx) {
        $menuGroup = $('<div class="menu-group clearfix"></div>');
        $menuContainer.append($menuGroup);

        this.$currentGroup = $menuGroup;
        this.currentGroupIdx = groupIdx;
      }
    };
    MenuContainer.fn._addOneMenu = function (menu) {
      let $menuNormal = menu.$domNormal;
      let $menuSelected = menu.$domSelected;

      let $menuGroup = this.$currentGroup;
      let $item = $('<div class="menu-item clearfix"></div>');
      $menuSelected.hide();
      $item.append($menuNormal).append($menuSelected);
      $menuGroup.append($item);

      return $item;
    };

  });
// menu 构造函数
  _e(function (E, $) {

    // 定义构造函数
    let Menu = function (opt) {
      this.editor = opt.editor;
      this.id = opt.id;
      this.title = opt.title;
      this.$domNormal = opt.$domNormal;
      this.$domSelected = opt.$domSelected || opt.$domNormal;

      // document.execCommand 的参数
      this.commandName = opt.commandName;
      this.commandValue = opt.commandValue;
      this.commandNameSelected = opt.commandNameSelected || opt.commandName;
      this.commandValueSelected = opt.commandValueSelected || opt.commandValue;
    };

    Menu.fn = Menu.prototype;

    // 暴露给 E
    E.Menu = Menu;
  });
// Menu.fn 初始化绑定的事件
  _e(function (E, $) {

    let Menu = E.Menu;

    // 初始化UI
    Menu.fn.initUI = function () {
      let editor = this.editor;
      let uiConfig = editor.UI.menus;
      let menuId = this.id;
      let menuUI = uiConfig[menuId];

      if (this.$domNormal && this.$domSelected) {
        // 自定义的菜单中，已经传入了 $dom 无需从配置文件中查找生成
        return;
      }

      if (menuUI == null) {
        E.warn('editor.UI配置中，没有菜单 "' + menuId + '" 的UI配置，只能取默认值');

        // 必须写成 uiConfig['default'];
        // 写成 uiConfig.default IE8会报错
        menuUI = uiConfig['default'];
      }

      // 正常状态
      this.$domNormal = $(menuUI.normal);

      // 选中状态
      if (/^\./.test(menuUI.selected)) {
        // 增加一个样式
        this.$domSelected = this.$domNormal.clone().addClass(menuUI.selected.slice(1));
      } else {
        // 一个新的dom对象
        this.$domSelected = $(menuUI.selected);
      }
    };

  });
// Menu.fn API
  _e(function (E, $) {

    let Menu = E.Menu;

    // 渲染菜单
    Menu.fn.render = function (groupIdx) {
      // 渲染UI
      this.initUI();

      let editor = this.editor;
      let menuContainer = editor.menuContainer;
      let $menuItem = menuContainer.appendMenu(groupIdx, this);
      let onRender = this.onRender;

      // 渲染tip
      this._renderTip($menuItem);

      // 执行 onRender 函数
      if (onRender && typeof onRender === 'function') {
        onRender.call(this);
      }
    };
    Menu.fn._renderTip = function ($menuItem) {
      let self = this;
      let editor = self.editor;
      let title = self.title;
      let $tip = $('<div class="menu-tip"></div>');
      // let $triangle = $('<i class="tip-triangle"></i>'); // 小三角

      // 计算 tip 宽度
      let $tempDiv;
      if (!self.tipWidth) {
        // 设置一个纯透明的 p（absolute;top:-10000px;不会显示在内容区域）
        // 内容赋值为 title ，为了计算tip宽度
        $tempDiv = $('<p style="opacity:0; filter:Alpha(opacity=0); position:absolute;top:-10000px;">' + title + '</p>');
        // 先添加到body，计算完再 remove
        E.$body.append($tempDiv);
        editor.ready(function () {
          let editor = this;
          let titleWidth = $tempDiv.outerWidth() + 5; // 多出 5px 的冗余
          let currentWidth = $tip.outerWidth();
          let currentMarginLeft = parseFloat($tip.css('margin-left'), 10);
          // 计算完，拿到数据，则弃用
          $tempDiv.remove();
          $tempDiv = null;

          // 重新设置样式
          $tip.css({
            width: titleWidth,
            'margin-left': currentMarginLeft + (currentWidth - titleWidth) / 2
          });

          // 存储
          self.tipWidth = titleWidth;
        });
      }

      // $tip.append($triangle);
      $tip.append(title);
      $menuItem.append($tip);

      function show() {
        $tip.show();
      }

      function hide() {
        $tip.hide();
      }

      let timeoutId;
      $menuItem.find('a').on('mouseenter', function (e) {
        if (!self.active() && !self.disabled()) {
          timeoutId = setTimeout(show, 200);
        }
      }).on('mouseleave', function (e) {
        timeoutId && clearTimeout(timeoutId);
        hide();
      }).on('click', hide);
    };

    // 绑定事件
    Menu.fn.bindEvent = function () {
      let self = this;

      let $domNormal = self.$domNormal;
      let $domSelected = self.$domSelected;

      // 试图获取该菜单定义的事件（未selected），没有则自己定义
      let clickEvent = self.clickEvent;
      if (!clickEvent) {
        clickEvent = function (e) {
          // -----------dropPanel dropList modal-----------
          let dropObj = self.dropPanel || self.dropList || self.modal;
          if (dropObj && dropObj.show) {
            if (dropObj.isShowing) {
              dropObj.hide();
            } else {
              dropObj.show();
            }
            return;
          }

          // -----------command-----------
          let editor = self.editor;
          let commandName;
          let commandValue;

          let selected = self.selected;
          if (selected) {
            commandName = self.commandNameSelected;
            commandValue = self.commandValueSelected;
          } else {
            commandName = self.commandName;
            commandValue = self.commandValue;
          }

          if (commandName) {
            // 执行命令
            editor.command(e, commandName, commandValue);
          } else {
            // 提示
            E.warn('菜单 "' + self.id + '" 未定义click事件');
            e.preventDefault();
          }
        };
      }
      // 获取菜单定义的selected情况下的点击事件
      let clickEventSelected = self.clickEventSelected || clickEvent;

      // 将事件绑定到菜单dom上
      $domNormal.click(function (e) {
        if (!self.disabled()) {
          clickEvent.call(self, e);
          self.updateSelected();
        }
        e.preventDefault();
      });
      $domSelected.click(function (e) {
        if (!self.disabled()) {
          clickEventSelected.call(self, e);
          self.updateSelected();
        }
        e.preventDefault();
      });
    };

    // 更新选中状态
    Menu.fn.updateSelected = function () {
      let self = this;
      let editor = self.editor;

      // 试图获取用户自定义的判断事件
      let updateSelectedEvent = self.updateSelectedEvent;
      if (!updateSelectedEvent) {
        // 用户未自定义，则设置默认值
        updateSelectedEvent = function () {
          let self = this;
          let editor = self.editor;
          let commandName = self.commandName;
          let commandValue = self.commandValue;

          if (commandValue) {
            if (editor.queryCommandValue(commandName).toLowerCase() === commandValue.toLowerCase()) {
              return true;
            }
          } else if (editor.queryCommandState(commandName)) {
            return true;
          }

          return false;
        };
      }

      // 获取结果
      let result = updateSelectedEvent.call(self);
      result = !!result;

      // 存储结果、显示效果
      self.changeSelectedState(result);
    };

    // 切换选中状态、显示效果
    Menu.fn.changeSelectedState = function (state) {
      let self = this;
      let selected = self.selected;

      if (state != null && typeof state === 'boolean') {
        if (selected === state) {
          // 计算结果和当前的状态一样
          return;
        }
        // 存储结果
        self.selected = state;

        // 切换菜单的显示
        if (state) {
          // 选中
          self.$domNormal.hide();
          self.$domSelected.show();
        } else {
          // 未选中
          self.$domNormal.show();
          self.$domSelected.hide();
        }
      } // if
    };

    // 点击菜单，显示了 dropPanel modal 时，菜单的状态
    Menu.fn.active = function (active) {
      if (active == null) {
        return this._activeState;
      }
      this._activeState = active;
    };
    Menu.fn.activeStyle = function (active) {
      let selected = this.selected;
      let $dom = this.$domNormal;
      let $domSelected = this.$domSelected;

      if (active) {
        $dom.addClass('active');
        $domSelected.addClass('active');
      } else {
        $dom.removeClass('active');
        $domSelected.removeClass('active');
      }

      // 记录状态 （ menu hover 时会取状态用 ）
      this.active(active);
    };

    // 菜单的启用和禁用
    Menu.fn.disabled = function (opt) {
      // 参数为空，取值
      if (opt == null) {
        return !!this._disabled;
      }

      if (this._disabled === opt) {
        // 要设置的参数值和当前参数只一样，无需再次设置
        return;
      }

      let $dom = this.$domNormal;
      let $domSelected = this.$domSelected;

      // 设置样式
      if (opt) {
        $dom.addClass('disable');
        $domSelected.addClass('disable');
      } else {
        $dom.removeClass('disable');
        $domSelected.removeClass('disable');
      }

      // 存储
      this._disabled = opt;
    };

  });
// dropList 构造函数
  _e(function (E, $) {

    // 定义构造函数
    let DropList = function (editor, menu, opt) {
      this.editor = editor;
      this.menu = menu;

      // list 的数据源，格式 {'commandValue': 'title', ...}
      this.data = opt.data;
      // 要为每个item自定义的模板
      this.tpl = opt.tpl;
      // 为了执行 editor.commandForElem 而传入的elem查询方式
      this.selectorForELemCommand = opt.selectorForELemCommand;

      // 执行事件前后的钩子
      this.beforeEvent = opt.beforeEvent;
      this.afterEvent = opt.afterEvent;

      // 初始化
      this.init();
    };

    DropList.fn = DropList.prototype;

    // 暴露给 E
    E.DropList = DropList;
  });
// dropList fn bind
  _e(function (E, $) {

    let DropList = E.DropList;

    // init
    DropList.fn.init = function () {
      let self = this;

      // 生成dom对象
      self.initDOM();

      // 绑定command事件
      self.bindEvent();

      // 声明隐藏的事件
      self.initHideEvent();
    };

    // 初始化dom结构
    DropList.fn.initDOM = function () {
      let self = this;
      let data = self.data;
      let tpl = self.tpl || '<span>{#title}</span>';
      let $list = $('<div class="wangEditor-drop-list clearfix"></div>');

      let itemContent;
      let $item;
      $.each(data, function (commandValue, title) {
        itemContent = tpl.replace(/{#commandValue}/ig, commandValue).replace(/{#title}/ig, title);
        $item = $('<a href="#" commandValue="' + commandValue + '"></a>');
        $item.append(itemContent);
        $list.append($item);
      });

      self.$list = $list;
    };

    // 绑定事件
    DropList.fn.bindEvent = function () {
      let self = this;
      let editor = self.editor;
      let menu = self.menu;
      let commandName = menu.commandName;
      let selectorForELemCommand = self.selectorForELemCommand;
      let $list = self.$list;

      // 执行事件前后的钩子函数
      let beforeEvent = self.beforeEvent;
      let afterEvent = self.afterEvent;

      $list.on('click', 'a[commandValue]', function (e) {
        // 正式命令执行之前
        if (beforeEvent && typeof beforeEvent === 'function') {
          beforeEvent.call(e);
        }

        // 执行命令
        let commandValue = $(e.currentTarget).attr('commandValue');
        if (menu.selected && editor.isRangeEmpty() && selectorForELemCommand) {
          // 当前处于选中状态，并且选中内容为空
          editor.commandForElem(selectorForELemCommand, e, commandName, commandValue);
        } else {
          // 当前未处于选中状态，或者有选中内容。则执行默认命令
          editor.command(e, commandName, commandValue);
        }

        // 正式命令之后的钩子
        if (afterEvent && typeof afterEvent === 'function') {
          afterEvent.call(e);
        }
      });
    };

    // 点击其他地方，立即隐藏 droplist
    DropList.fn.initHideEvent = function () {
      let self = this;

      // 获取 list elem
      let thisList = self.$list.get(0);

      E.$body.on('click', function (e) {
        if (!self.isShowing) {
          return;
        }
        let trigger = e.target;

        // 获取菜单elem
        let menu = self.menu;
        let menuDom;
        if (menu.selected) {
          menuDom = menu.$domSelected.get(0);
        } else {
          menuDom = menu.$domNormal.get(0);
        }

        if (menuDom === trigger || $.contains(menuDom, trigger)) {
          // 说明由本菜单点击触发的
          return;
        }

        if (thisList === trigger || $.contains(thisList, trigger)) {
          // 说明由本list点击触发的
          return;
        }

        // 其他情况，隐藏 list
        self.hide();
      });

      E.$window.scroll(function () {
        self.hide();
      });

      E.$window.on('resize', function () {
        self.hide();
      });
    };

  });
// dropListfn api
  _e(function (E, $) {

    let DropList = E.DropList;

    // 渲染
    DropList.fn._render = function () {
      let self = this;
      let editor = self.editor;
      let $list = self.$list;

      // 渲染到页面
      editor.$editorContainer.append($list);

      // 记录状态
      self.rendered = true;
    };

    // 定位
    DropList.fn._position = function () {
      let self = this;
      let $list = self.$list;
      let editor = self.editor;
      let menu = self.menu;
      let $menuContainer = editor.menuContainer.$menuContainer;
      let $menuDom = menu.selected ? menu.$domSelected : menu.$domNormal;
      // 注意这里的 offsetParent() 要返回 .menu-item 的 position
      // 因为 .menu-item 是 position:relative
      let menuPosition = $menuDom.offsetParent().position();

      // 取得 menu 的位置、尺寸属性
      let menuTop = menuPosition.top;
      let menuLeft = menuPosition.left;
      let menuHeight = $menuDom.offsetParent().height();
      let menuWidth = $menuDom.offsetParent().width();

      // 取得 list 的尺寸属性
      let listWidth = $list.outerWidth();
      // let listHeight = $list.outerHeight();

      // 取得 $txt 的尺寸
      let txtWidth = editor.txt.$txt.outerWidth();

      // ------------开始计算-------------

      // 初步计算 list 位置属性
      let top = menuTop + menuHeight;
      let left = menuLeft + menuWidth / 2;
      let marginLeft = 0 - menuWidth / 2;

      // 如果超出了有边界，则要左移（且和右侧有间隙）
      let valWithTxt = (left + listWidth) - txtWidth;
      if (valWithTxt > -10) {
        marginLeft = marginLeft - valWithTxt - 10;
      }
      // 设置样式
      $list.css({
        top: top,
        left: left,
        'margin-left': marginLeft
      });

      // 如果因为向下滚动而导致菜单fixed，则再加一步处理
      if (editor._isMenufixed) {
        top = top + (($menuContainer.offset().top + $menuContainer.outerHeight()) - $list.offset().top);

        // 重新设置top
        $list.css({
          top: top
        });
      }
    };

    // 显示
    DropList.fn.show = function () {
      let self = this;
      let menu = self.menu;
      if (!self.rendered) {
        // 第一次show之前，先渲染
        self._render();
      }

      if (self.isShowing) {
        return;
      }

      let $list = self.$list;
      $list.show();

      // 定位
      self._position();

      // 记录状态
      self.isShowing = true;

      // 菜单状态
      menu.activeStyle(true);
    };

    // 隐藏
    DropList.fn.hide = function () {
      let self = this;
      let menu = self.menu;
      if (!self.isShowing) {
        return;
      }

      let $list = self.$list;
      $list.hide();

      // 记录状态
      self.isShowing = false;

      // 菜单状态
      menu.activeStyle(false);
    };
  });
// dropPanel 构造函数
  _e(function (E, $) {

    // 定义构造函数
    let DropPanel = function (editor, menu, opt) {
      this.editor = editor;
      this.menu = menu;
      this.$content = opt.$content;
      this.width = opt.width || 200;
      this.height = opt.height;
      this.onRender = opt.onRender;

      // init
      this.init();
    };

    DropPanel.fn = DropPanel.prototype;

    // 暴露给 E
    E.DropPanel = DropPanel;
  });
// dropPanel fn bind
  _e(function (E, $) {

    let DropPanel = E.DropPanel;

    // init
    DropPanel.fn.init = function () {
      let self = this;

      // 生成dom对象
      self.initDOM();

      // 声明隐藏的事件
      self.initHideEvent();
    };

    // init DOM
    DropPanel.fn.initDOM = function () {
      let self = this;
      let $content = self.$content;
      let width = self.width;
      let height = self.height;
      let $panel = $('<div class="wangEditor-drop-panel clearfix"></div>');
      let $triangle = $('<div class="tip-triangle"></div>');

      $panel.css({
        width: width,
        height: height ? height : 'auto'
      });
      $panel.append($triangle);
      $panel.append($content);

      // 添加对象数据
      self.$panel = $panel;
      self.$triangle = $triangle;
    };

    // 点击其他地方，立即隐藏 dropPanel
    DropPanel.fn.initHideEvent = function () {
      let self = this;

      // 获取 panel elem
      let thisPanle = self.$panel.get(0);

      E.$body.on('click', function (e) {
        if (!self.isShowing) {
          return;
        }
        let trigger = e.target;

        // 获取菜单elem
        let menu = self.menu;
        let menuDom;
        if (menu.selected) {
          menuDom = menu.$domSelected.get(0);
        } else {
          menuDom = menu.$domNormal.get(0);
        }

        if (menuDom === trigger || $.contains(menuDom, trigger)) {
          // 说明由本菜单点击触发的
          return;
        }

        if (thisPanle === trigger || $.contains(thisPanle, trigger)) {
          // 说明由本panel点击触发的
          return;
        }

        // 其他情况，隐藏 panel
        self.hide();
      });

      E.$window.scroll(function (e) {
        self.hide();
      });

      E.$window.on('resize', function () {
        self.hide();
      });
    };

  });
// dropPanel fn api
  _e(function (E, $) {

    let DropPanel = E.DropPanel;

    // 渲染
    DropPanel.fn._render = function () {
      let self = this;
      let onRender = self.onRender;
      let editor = self.editor;
      let $panel = self.$panel;

      // 渲染到页面
      editor.$editorContainer.append($panel);

      // 渲染后的回调事件
      onRender && onRender.call(self);

      // 记录状态
      self.rendered = true;
    };

    // 定位
    DropPanel.fn._position = function () {
      let self = this;
      let $panel = self.$panel;
      let $triangle = self.$triangle;
      let editor = self.editor;
      let $menuContainer = editor.menuContainer.$menuContainer;
      let menu = self.menu;
      let $menuDom = menu.selected ? menu.$domSelected : menu.$domNormal;
      // 注意这里的 offsetParent() 要返回 .menu-item 的 position
      // 因为 .menu-item 是 position:relative
      let menuPosition = $menuDom.offsetParent().position();

      // 取得 menu 的位置、尺寸属性
      let menuTop = menuPosition.top;
      let menuLeft = menuPosition.left;
      let menuHeight = $menuDom.offsetParent().height();
      let menuWidth = $menuDom.offsetParent().width();

      // 取得 panel 的尺寸属性
      let panelWidth = $panel.outerWidth();
      // let panelHeight = $panel.outerHeight();

      // 取得 $txt 的尺寸
      let txtWidth = editor.txt.$txt.outerWidth();

      // ------------开始计算-------------

      // 初步计算 panel 位置属性
      let top = menuTop + menuHeight;
      let left = menuLeft + menuWidth / 2;
      let marginLeft = 0 - panelWidth / 2;
      let marginLeft2 = marginLeft;  // 下文用于和 marginLeft 比较，来设置三角形tip的位置

      // 如果超出了左边界，则移动回来（要和左侧有10px间隙）
      if ((0 - marginLeft) > (left - 10)) {
        marginLeft = 0 - (left - 10);
      }

      // 如果超出了有边界，则要左移（且和右侧有10px间隙）
      let valWithTxt = (left + panelWidth + marginLeft) - txtWidth;
      if (valWithTxt > -10) {
        marginLeft = marginLeft - valWithTxt - 10;
      }

      // 设置样式
      $panel.css({
        top: top,
        left: left,
        'margin-left': marginLeft
      });

      // 如果因为向下滚动而导致菜单fixed，则再加一步处理
      if (editor._isMenufixed) {
        top = top + (($menuContainer.offset().top + $menuContainer.outerHeight()) - $panel.offset().top);

        // 重新设置top
        $panel.css({
          top: top
        });
      }

      // 设置三角形 tip 的位置
      $triangle.css({
        'margin-left': marginLeft2 - marginLeft - 5
      });
    };

    // focus 第一个 input
    DropPanel.fn.focusFirstInput = function () {
      let self = this;
      let $panel = self.$panel;
      $panel.find('input[type=text],textarea').each(function () {
        let $input = $(this);
        if ($input.attr('disabled') == null) {
          $input.focus();
          return false;
        }
      });
    };

    // 显示
    DropPanel.fn.show = function () {
      let self = this;
      let menu = self.menu;
      if (!self.rendered) {
        // 第一次show之前，先渲染
        self._render();
      }

      if (self.isShowing) {
        return;
      }

      let $panel = self.$panel;
      $panel.show();

      // 定位
      self._position();

      // 记录状态
      self.isShowing = true;

      // 菜单状态
      menu.activeStyle(true);

      if (E.w3cRange) {
        // 高级浏览器
        self.focusFirstInput();
      } else {
        // 兼容 IE8 input placeholder
        E.placeholderForIE8($panel);
      }
    };

    // 隐藏
    DropPanel.fn.hide = function () {
      let self = this;
      let menu = self.menu;
      if (!self.isShowing) {
        return;
      }

      let $panel = self.$panel;
      $panel.hide();

      // 记录状态
      self.isShowing = false;

      // 菜单状态
      menu.activeStyle(false);
    };

  });
// modal 构造函数
  _e(function (E, $) {

    // 定义构造函数
    let Modal = function (editor, menu, opt) {
      this.editor = editor;
      this.menu = menu;
      this.$content = opt.$content;

      this.init();
    };

    Modal.fn = Modal.prototype;

    // 暴露给 E
    E.Modal = Modal;
  });
// modal fn bind
  _e(function (E, $) {

    let Modal = E.Modal;

    Modal.fn.init = function () {
      let self = this;

      // 初始化dom
      self.initDom();

      // 初始化隐藏事件
      self.initHideEvent();
    };

    // 初始化dom
    Modal.fn.initDom = function () {
      let self = this;
      let $content = self.$content;
      let $modal = $('<div class="wangEditor-modal"></div>');
      let $close = $('<div class="wangEditor-modal-close"><i class="wangeditor-menu-img-cancel-circle"></i></div>');

      $modal.append($close);
      $modal.append($content);

      // 记录数据
      self.$modal = $modal;
      self.$close = $close;
    };

    // 初始化隐藏事件
    Modal.fn.initHideEvent = function () {
      let self = this;
      let $close = self.$close;
      let modal = self.$modal.get(0);

      // 点击 $close 按钮，隐藏
      $close.click(function () {
        self.hide();
      });

      // 点击其他部分，隐藏
      E.$body.on('click', function (e) {
        if (!self.isShowing) {
          return;
        }
        let trigger = e.target;

        // 获取菜单elem
        let menu = self.menu;
        let menuDom;
        if (menu) {
          if (menu.selected) {
            menuDom = menu.$domSelected.get(0);
          } else {
            menuDom = menu.$domNormal.get(0);
          }

          if (menuDom === trigger || $.contains(menuDom, trigger)) {
            // 说明由本菜单点击触发的
            return;
          }
        }

        if (modal === trigger || $.contains(modal, trigger)) {
          // 说明由本panel点击触发的
          return;
        }

        // 其他情况，隐藏 panel
        self.hide();
      });
    };
  });
// modal fn api
  _e(function (E, $) {

    let Modal = E.Modal;

    // 渲染
    Modal.fn._render = function () {
      let self = this;
      let editor = self.editor;
      let $modal = self.$modal;

      // $modal的z-index，在配置的z-index基础上再 +10
      $modal.css('z-index', editor.config.zindex + 10 + '');

      // 渲染到body最后面
      E.$body.append($modal);

      // 记录状态
      self.rendered = true;
    };

    // 定位
    Modal.fn._position = function () {
      let self = this;
      let $modal = self.$modal;
      let top = $modal.offset().top;
      let width = $modal.outerWidth();
      let height = $modal.outerHeight();
      let marginLeft = 0 - (width / 2);
      let marginTop = 0 - (height / 2);
      let sTop = E.$window.scrollTop();

      // 保证modal最顶部，不超过浏览器上边框
      if ((height / 2) > top) {
        marginTop = 0 - top;
      }

      $modal.css({
        'margin-left': marginLeft + 'px',
        'margin-top': (marginTop + sTop) + 'px'
      });
    };

    // 显示
    Modal.fn.show = function () {
      let self = this;
      let menu = self.menu;
      if (!self.rendered) {
        // 第一次show之前，先渲染
        self._render();
      }

      if (self.isShowing) {
        return;
      }
      // 记录状态
      self.isShowing = true;

      let $modal = self.$modal;
      $modal.show();

      // 定位
      self._position();

      // 激活菜单状态
      menu && menu.activeStyle(true);
    };

    // 隐藏
    Modal.fn.hide = function () {
      let self = this;
      let menu = self.menu;
      if (!self.isShowing) {
        return;
      }
      // 记录状态
      self.isShowing = false;

      // 隐藏
      let $modal = self.$modal;
      $modal.hide();

      // 菜单状态
      menu && menu.activeStyle(false);
    };
  });
// txt 构造函数
  _e(function (E, $) {

    // 定义构造函数
    let Txt = function (editor) {
      this.editor = editor;

      // 初始化
      this.init();
    };

    Txt.fn = Txt.prototype;

    // 暴露给 E
    E.Txt = Txt;
  });
// Txt.fn bind fn
  _e(function (E, $) {

    let Txt = E.Txt;

    // 初始化
    Txt.fn.init = function () {
      let self = this;
      let editor = self.editor;
      let $valueContainer = editor.$valueContainer;
      let currentValue = editor.getInitValue();
      let $txt;

      if ($valueContainer.get(0).nodeName === 'DIV') {
        // 如果传入生成编辑器的元素就是div，则直接使用
        $txt = $valueContainer;
        $txt.addClass("wangEditor-txt");
        $txt.attr('contentEditable', 'true');
      } else {
        // 如果不是div（是textarea），则创建一个div
        $txt = $(
          '<div class="wangEditor-txt" contentEditable="true">' +
          currentValue +
          '</div>'
        );
      }

      // 试图最后插入一个空行，ready之后才行
      editor.ready(function () {
        self.insertEmptyP();
      });

      self.$txt = $txt;

      // 删除时，如果没有内容了，就添加一个 <p><br></p>
      self.contentEmptyHandle();

      // enter时，不能使用 div 换行
      self.bindEnterForDiv();

      // enter时，用 p 包裹 text
      self.bindEnterForText();

      // tab 插入4个空格
      self.bindTabEvent();

      // 处理粘贴内容
      self.bindPasteFilter();

      // $txt.formatText() 方法
      self.bindFormatText();

      // 定义 $txt.html() 方法
      self.bindHtml();
    };

    // 删除时，如果没有内容了，就添加一个 <p><br></p>
    Txt.fn.contentEmptyHandle = function () {
      let self = this;
      let editor = self.editor;
      let $txt = self.$txt;
      let $p;

      $txt.on('keydown', function (e) {
        if (e.keyCode !== 8) {
          return;
        }
        let txtHtml = $.trim($txt.html().toLowerCase());
        if (txtHtml === '<p><br></p>') {
          // 如果最后还剩余一个空行，就不再继续删除了
          e.preventDefault();
          return;
        }
      });

      $txt.on('keyup', function (e) {
        if (e.keyCode !== 8) {
          return;
        }
        let txtHtml = $.trim($txt.html().toLowerCase());
        // ff时用 txtHtml === '<br>' 判断，其他用 !txtHtml 判断
        if (!txtHtml || txtHtml === '<br>') {
          // 内容空了
          $p = $('<p><br/></p>');
          $txt.html(''); // 一定要先清空，否则在 ff 下有问题
          $txt.append($p);
          editor.restoreSelectionByElem($p.get(0));
        }
      });
    };

    // enter时，不能使用 div 换行
    Txt.fn.bindEnterForDiv = function () {
      let tags = E.config.legalTags; // 配置中编辑器要求的合法标签，如 p head table blockquote ul ol 等
      let self = this;
      let editor = self.editor;
      let $txt = self.$txt;

      let $keydownDivElem;

      function divHandler() {
        if (!$keydownDivElem) {
          return;
        }

        let $pElem = $('<p>' + $keydownDivElem.html() + '</p>');
        $keydownDivElem.after($pElem);
        $keydownDivElem.remove();
      }

      $txt.on('keydown keyup', function (e) {
        if (e.keyCode !== 13) {
          return;
        }
        // 查找合法标签
        let rangeElem = editor.getRangeElem();
        let targetElem = editor.getLegalTags(rangeElem);
        let $targetElem;
        let $pElem;

        if (!targetElem) {
          // 没找到合法标签，就去查找 div
          targetElem = editor.getSelfOrParentByName(rangeElem, 'div');
          if (!targetElem) {
            return;
          }
          $targetElem = $(targetElem);

          if (e.type === 'keydown') {
            // 异步执行（同步执行会出现问题）
            $keydownDivElem = $targetElem;
            setTimeout(divHandler, 0);
          }

          if (e.type === 'keyup') {
            // 将 div 的内容移动到 p 里面，并移除 div
            $pElem = $('<p>' + $targetElem.html() + '</p>');
            $targetElem.after($pElem);
            $targetElem.remove();

            // 如果是回车结束，将选区定位到行首
            editor.restoreSelectionByElem($pElem.get(0), 'start');
          }
        }
      });
    };

    // enter时，用 p 包裹 text
    Txt.fn.bindEnterForText = function () {
      let self = this;
      let $txt = self.$txt;
      let handle;
      $txt.on('keyup', function (e) {
        if (e.keyCode !== 13) {
          return;
        }
        if (!handle) {
          handle = function () {
            self.wrapImgAndText();
          };
        }
        setTimeout(handle);
      });
    };

    // tab 时，插入4个空格
    Txt.fn.bindTabEvent = function () {
      let self = this;
      let editor = self.editor;
      let $txt = self.$txt;

      $txt.on('keydown', function (e) {
        if (e.keyCode !== 9) {
          // 只监听 tab 按钮
          return;
        }
        // 如果浏览器支持 insertHtml 则插入4个空格。如果不支持，就不管了
        if (editor.queryCommandSupported('insertHtml')) {
          editor.command(e, 'insertHtml', '&nbsp;&nbsp;&nbsp;&nbsp;');
        }
      });
    };

    // 处理粘贴内容
    Txt.fn.bindPasteFilter = function () {
      let self = this;
      let editor = self.editor;
      let resultHtml = '';  //存储最终的结果
      let $txt = self.$txt;
      let legalTags = editor.config.legalTags;
      let legalTagArr = legalTags.split(',');

      $txt.on('paste', function (e) {
        if (!editor.config.pasteFilter) {
          // 配置中取消了粘贴过滤
          return;
        }

        let currentNodeName = editor.getRangeElem().nodeName;
        if (currentNodeName === 'TD' || currentNodeName === 'TH') {
          // 在表格的单元格中粘贴，忽略所有内容。否则会出现异常情况
          return;
        }

        resultHtml = ''; // 先清空 resultHtml

        let pasteHtml, $paste;
        let data = e.clipboardData || e.originalEvent.clipboardData;
        let ieData = window.clipboardData;

        if (editor.config.pasteText) {
          // 只粘贴纯文本

          if (data && data.getData) {
            // w3c
            pasteHtml = data.getData('text/plain');
          } else if (ieData && ieData.getData) {
            // IE
            pasteHtml = ieData.getData('text');
          } else {
            // 其他情况
            return;
          }

          // 拼接为 <p> 标签
          if (pasteHtml) {
            resultHtml = '<p>' + pasteHtml + '</p>';
          }

        } else {
          // 粘贴过滤了样式的、只有标签的 html

          if (data && data.getData) {
            // w3c

            // 获取粘贴过来的html
            pasteHtml = data.getData('text/html');
            if (pasteHtml) {
              // 创建dom
              $paste = $('<div>' + pasteHtml + '</div>');
              // 处理，并将结果存储到 resultHtml 『全局』变量
              handle($paste.get(0));
            } else {
              // 得不到html，试图获取text
              pasteHtml = data.getData('text/plain');
              if (pasteHtml) {
                // 替换特殊字符 李爽处理。 作者有毛病吗？ 直接替换成 &nbsp;了事儿？
                //pasteHtml = pasteHtml.replace(/[ ]/g, '&nbsp;')
                pasteHtml = pasteHtml.replace(/[ ]/g, ' ')
                  .replace(/</g, '&lt;')
                  .replace(/>/g, '&gt;')
                  .replace(/\n/g, '</p><p>');
                // 拼接
                resultHtml = '<p>' + pasteHtml + '</p>';

                // 查询链接
                resultHtml = resultHtml.replace(/<p>(https?:\/\/.*?)<\/p>/ig, function (match, link) {
                  return '<p><a href="' + link + '" target="_blank">' + link + '</p>';
                });
              }
            }

          } else if (ieData && ieData.getData) {
            // IE 直接从剪切板中取出纯文本格式
            resultHtml = ieData.getData('text');
            if (!resultHtml) {
              return;
            }
            // 拼接为 <p> 标签
            resultHtml = '<p>' + resultHtml + '</p>';
            resultHtml = resultHtml.replace(new RegExp('\n', 'g'), '</p><p>');
          } else {
            // 其他情况
            return;
          }
        }

        // 执行命令
        if (resultHtml) {
          editor.command(e, 'insertHtml', resultHtml);

          // 删除内容为空的 p 和嵌套的 p
          self.clearEmptyOrNestP();
        }
      });

      // 处理粘贴的内容
      function handle(elem) {
        if (!elem || !elem.nodeType || !elem.nodeName) {
          return;
        }
        let $elem;
        let nodeName = elem.nodeName.toLowerCase();
        let nodeType = elem.nodeType;
        let childNodesClone;

        // 只处理文本和普通node标签
        if (nodeType !== 3 && nodeType !== 1) {
          return;
        }

        $elem = $(elem);

        // 如果是容器，则继续深度遍历
        if (nodeName === 'div') {
          childNodesClone = [];
          $.each(elem.childNodes, function (index, item) {
            // elem.childNodes 可获取TEXT节点，而 $elem.children() 就获取不到
            // 先将 elem.childNodes 拷贝一份，一面在循环递归过程中 elem 发生变化
            childNodesClone.push(item);
          });
          // 遍历子元素，执行操作
          $.each(childNodesClone, function () {
            handle(this);
          });
          return;
        }

        if (legalTagArr.indexOf(nodeName) >= 0) {
          // 如果是合法标签之内的，则根据元素类型，获取值
          resultHtml += getResult(elem);
        } else if (nodeType === 3) {
          // 如果是文本，则直接插入 p 标签
          resultHtml += '<p>' + elem.textContent + '</p>';
        } else if (nodeName === 'br') {
          // <br>保留
          resultHtml += '<br/>';
        }
        else {
          // 忽略的标签
          if (['meta', 'style', 'script', 'object', 'form', 'iframe', 'hr'].indexOf(nodeName) >= 0) {
            return;
          }
          // 其他标签，移除属性，插入 p 标签
          $elem = $(removeAttrs(elem));
          // 注意，这里的 clone() 是必须的，否则会出错
          resultHtml += $('<div>').append($elem.clone()).html();
        }
      }

      // 获取元素的结果
      function getResult(elem) {
        let nodeName = elem.nodeName.toLowerCase();
        let $elem;
        let htmlForP = '';
        let htmlForLi = '';

        if (['blockquote'].indexOf(nodeName) >= 0) {

          // 直接取出元素text即可
          $elem = $(elem);
          return '<' + nodeName + '>' + $elem.text() + '</' + nodeName + '>';

        } else if (['p', 'h1', 'h2', 'h3', 'h4', 'h5'].indexOf(nodeName) >= 0) {

          //p head 取出 text 和链接
          elem = removeAttrs(elem);
          $elem = $(elem);
          htmlForP = $elem.html();

          // 剔除 a img 之外的元素
          htmlForP = htmlForP.replace(/<.*?>/ig, function (tag) {
            if (tag === '</a>' || tag.indexOf('<a ') === 0 || tag.indexOf('<img ') === 0) {
              return tag;
            } else {
              return '';
            }
          });

          return '<' + nodeName + '>' + htmlForP + '</' + nodeName + '>';

        } else if (['ul', 'ol'].indexOf(nodeName) >= 0) {

          // ul ol元素，获取子元素（li元素）的text link img，再拼接
          $elem = $(elem);
          $elem.children().each(function () {
            let $li = $(removeAttrs(this));
            let html = $li.html();

            html = html.replace(/<.*?>/ig, function (tag) {
              if (tag === '</a>' || tag.indexOf('<a ') === 0 || tag.indexOf('<img ') === 0) {
                return tag;
              } else {
                return '';
              }
            });

            htmlForLi += '<li>' + html + '</li>';
          });
          return '<' + nodeName + '>' + htmlForLi + '</' + nodeName + '>';

        } else {

          // 其他元素，移除元素属性
          $elem = $(removeAttrs(elem));
          return $('<div>').append($elem).html();
        }
      }

      // 移除一个元素（子元素）的attr
      function removeAttrs(elem) {
        let attrs = elem.attributes || [];
        let attrNames = [];
        let exception = ['href', 'target', 'src', 'alt', 'rowspan', 'colspan']; //例外情况

        // 先存储下elem中所有 attr 的名称
        $.each(attrs, function (key, attr) {
          if (attr && attr.nodeType === 2) {
            attrNames.push(attr.nodeName);
          }
        });
        // 再根据名称删除所有attr
        $.each(attrNames, function (key, attr) {
          if (exception.indexOf(attr) < 0) {
            // 除了 exception 规定的例外情况，删除其他属性
            elem.removeAttribute(attr);
          }
        });


        // 递归子节点
        let children = elem.childNodes;
        if (children.length) {
          $.each(children, function (key, value) {
            removeAttrs(value);
          });
        }

        return elem;
      }
    };

    // 绑定 $txt.formatText() 方法
    Txt.fn.bindFormatText = function () {
      let self = this;
      let editor = self.editor;
      let $txt = self.$txt;
      let legalTags = E.config.legalTags;
      let legalTagArr = legalTags.split(',');
      let length = legalTagArr.length;
      let regArr = [];

      // 将 E.config.legalTags 配置的有效字符，生成正则表达式
      $.each(legalTagArr, function (k, tag) {
        let reg = '\>\\s*\<(' + tag + ')\>';
        regArr.push(new RegExp(reg, 'ig'));
      });

      // 增加 li
      regArr.push(new RegExp('\>\\s*\<(li)\>', 'ig'));

      // 增加 tr
      regArr.push(new RegExp('\>\\s*\<(tr)\>', 'ig'));

      // 增加 code
      regArr.push(new RegExp('\>\\s*\<(code)\>', 'ig'));

      // 生成 formatText 方法
      $txt.formatText = function () {
        let $temp = $('<div>');
        let html = $txt.html();

        // 去除空格
        html = html.replace(/\s*</ig, '<');

        // 段落、表格之间换行
        $.each(regArr, function (k, reg) {
          if (!reg.test(html)) {
            return;
          }
          html = html.replace(reg, function (matchStr, tag) {
            return '>\n<' + tag + '>';
          });
        });

        $temp.html(html);
        return $temp.text();
      };
    };

    // 定制 $txt.html 方法
    Txt.fn.bindHtml = function () {
      let self = this;
      let editor = self.editor;
      let $txt = self.$txt;
      let $valueContainer = editor.$valueContainer;
      let valueNodeName = editor.valueNodeName;

      $txt.html = function (html) {
        let result;

        if (valueNodeName === 'div') {
          // div 生成的编辑器，取值、赋值，都直接触发jquery的html方法
          result = $.fn.html.call($txt, html);
        }

        // textarea 生成的编辑器，则需要考虑赋值时，也给textarea赋值

        if (html === undefined) {
          // 取值，直接触发jquery原生html方法
          result = $.fn.html.call($txt);

          // 替换 html 中，src和href属性中的 & 字符。
          // 因为 .html() 或者 .innerHTML 会把所有的 & 字符都改成 &amp; 但是 src 和 href 中的要保持 &
          result = result.replace(/(href|src)\=\"(.*)\"/igm, function (a, b, c) {
            return b + '="' + c.replace('&amp;', '&') + '"';
          });
        } else {
          // 赋值，需要同时给 textarea 赋值
          result = $.fn.html.call($txt, html);
          $valueContainer.val(html);
        }

        if (html === undefined) {
          return result;
        } else {
          // 手动触发 change 事件，因为 $txt 监控了 change 事件来判断是否需要执行 editor.onchange
          $txt.change();
        }
      };
    };
  });
// Txt.fn api
  _e(function (E, $) {

    let Txt = E.Txt;

    let txtChangeEventNames = 'propertychange change click keyup input paste';

    // 渲染
    Txt.fn.render = function () {
      let $txt = this.$txt;
      let $editorContainer = this.editor.$editorContainer;
      $editorContainer.append($txt);
    };

    // 计算高度
    Txt.fn.initHeight = function () {
      let editor = this.editor;
      let $txt = this.$txt;
      let valueContainerHeight = editor.$valueContainer.height();
      let menuHeight = editor.menuContainer.height();
      let txtHeight = valueContainerHeight - menuHeight;

      // 限制最小为 50px
      txtHeight = txtHeight < 50 ? 50 : txtHeight;

      $txt.height(txtHeight);

      // 记录原始高度
      editor.valueContainerHeight = valueContainerHeight;

      // 设置 max-height
      this.initMaxHeight(txtHeight, menuHeight);
    };

    // 计算最大高度
    Txt.fn.initMaxHeight = function (txtHeight, menuHeight) {
      let editor = this.editor;
      let $menuContainer = editor.menuContainer.$menuContainer;
      let $txt = this.$txt;
      let $wrap = $('<div>');

      // 需要浏览器支持 max-height，否则不管
      if (window.getComputedStyle && 'max-height' in window.getComputedStyle($txt.get(0))) {
        // 获取 max-height 并判断是否有值
        let maxHeight = parseInt(editor.$valueContainer.css('max-height'));
        if (isNaN(maxHeight)) {
          return;
        }

        // max-height 和『全屏』暂时有冲突
        if (editor.menus.fullscreen) {
          E.warn('max-height和『全屏』菜单一起使用时，会有一些问题尚未解决，请暂时不要两个同时使用');
          return;
        }

        // 标记
        editor.useMaxHeight = true;

        // 设置maxheight
        $wrap.css({
          'max-height': (maxHeight - menuHeight) + 'px',
          'overflow-y': 'auto'
        });
        $txt.css({
          'height': 'auto',
          'overflow-y': 'visible',
          'min-height': txtHeight + 'px'
        });

        // 滚动式，菜单阴影
        $wrap.on('scroll', function () {
          if ($txt.parent().scrollTop() > 10) {
            $menuContainer.addClass('wangEditor-menu-shadow');
          } else {
            $menuContainer.removeClass('wangEditor-menu-shadow');
          }
        });

        // 需在编辑器区域外面再包裹一层
        $txt.wrap($wrap);
      }
    };

    // 保存选区
    Txt.fn.saveSelectionEvent = function () {
      let $txt = this.$txt;
      let editor = this.editor;
      let timeoutId;
      let dt = Date.now();

      function save() {
        editor.saveSelection();
      }

      // 同步保存选区
      function saveSync() {
        // 100ms之内，不重复保存
        if (Date.now() - dt < 100) {
          return;
        }

        dt = Date.now();
        save();
      }

      // 异步保存选区
      function saveAync() {
        // 节流，防止高频率重复操作
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
        timeoutId = setTimeout(save, 300);
      }

      // txt change 、focus、blur 时随时保存选区
      $txt.on(txtChangeEventNames + ' focus blur', function (e) {
        // 先同步保存选区，为了让接下来就马上要执行 editor.getRangeElem() 的程序
        // 能够获取到正确的 rangeElem
        saveSync();

        // 再异步保存选区，为了确定更加准确的选区，为后续的操作做准备
        saveAync();
      });

      // 鼠标拖拽选择时，可能会拖拽到编辑器区域外面再松手，此时 $txt 就监听不到 click事件了
      $txt.on('mousedown', function () {
        $txt.on('mouseleave.saveSelection', function (e) {
          // 先同步后异步，如上述注释
          saveSync();
          saveAync();

          // 顺道吧菜单状态也更新了
          editor.updateMenuStyle();
        });
      }).on('mouseup', function () {
        $txt.off('mouseleave.saveSelection');
      });

    };

    // 随时更新 value
    Txt.fn.updateValueEvent = function () {
      let $txt = this.$txt;
      let editor = this.editor;
      let timeoutId, oldValue;

      // 触发 onchange 事件
      function doOnchange() {
        let val = $txt.html();
        if (oldValue === val) {
          // 无变化
          return;
        }

        // 触发 onchange 事件
        if (editor.onchange && typeof editor.onchange === 'function') {
          editor.onchange.call(editor);
        }

        // 更新内容
        editor.updateValue();

        // 记录最新内容
        oldValue = val;
      }

      // txt change 时随时更新内容
      $txt.on(txtChangeEventNames, function (e) {
        // 初始化
        if (oldValue == null) {
          oldValue = $txt.html();
        }

        // 监控内容变化（停止操作 100ms 之后立即执行）
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
        timeoutId = setTimeout(doOnchange, 100);
      });
    };

    // 随时更新 menustyle
    Txt.fn.updateMenuStyleEvent = function () {
      let $txt = this.$txt;
      let editor = this.editor;

      // txt change 时随时更新内容
      $txt.on(txtChangeEventNames, function (e) {
        editor.updateMenuStyle();
      });
    };

    // 最后插入试图插入 <p><br><p>
    Txt.fn.insertEmptyP = function () {
      let $txt = this.$txt;
      let $children = $txt.children();

      if ($children.length === 0) {
        $txt.append($('<p><br></p>'));
        return;
      }

      if ($.trim($children.last().html()).toLowerCase() !== '<br>') {
        $txt.append($('<p><br></p>'));
      }
    };

    // 将编辑器暴露出来的文字和图片，都用 p 来包裹
    Txt.fn.wrapImgAndText = function () {
      let $txt = this.$txt;
      let $imgs = $txt.children('img');
      let txt = $txt[0];
      let childNodes = txt.childNodes;
      let childrenLength = childNodes.length;
      let i, childNode, p;

      // 处理图片
      $imgs.length && $imgs.each(function () {
        $(this).wrap('<p>');
      });

      // 处理文字
      for (i = 0; i < childrenLength; i++) {
        childNode = childNodes[i];
        if (childNode.nodeType === 3 && childNode.textContent && $.trim(childNode.textContent)) {
          $(childNode).wrap('<p>');
        }
      }
    };

    // 清空内容为空的<p>，以及重复包裹的<p>（在windows下的chrome粘贴文字之后，会出现上述情况）
    Txt.fn.clearEmptyOrNestP = function () {
      let $txt = this.$txt;
      let $pList = $txt.find('p');

      $pList.each(function () {
        let $p = $(this);
        let $children = $p.children();
        let childrenLength = $children.length;
        let $firstChild;
        let content = $.trim($p.html());

        // 内容为空的p
        if (!content) {
          $p.remove();
          return;
        }

        // 嵌套的p
        if (childrenLength === 1) {
          $firstChild = $children.first();
          if ($firstChild.get(0) && $firstChild.get(0).nodeName === 'P') {
            $p.html($firstChild.html());
          }
        }
      });
    };

    // 获取 scrollTop
    Txt.fn.scrollTop = function (val) {
      let self = this;
      let editor = self.editor;
      let $txt = self.$txt;

      if (editor.useMaxHeight) {
        return $txt.parent().scrollTop(val);
      } else {
        return $txt.scrollTop(val);
      }
    };

    // 鼠标hover时候，显示p、head的高度
    Txt.fn.showHeightOnHover = function () {
      let editor = this.editor;
      let $editorContainer = editor.$editorContainer;
      let menuContainer = editor.menuContainer;
      let $txt = this.$txt;
      let $tip = $('<i class="height-tip"><i>');
      let isTipInTxt = false;

      function addAndShowTip($target) {
        if (!isTipInTxt) {
          $editorContainer.append($tip);
          isTipInTxt = true;
        }

        let txtTop = $txt.position().top;
        let txtHeight = $txt.outerHeight();

        let height = $target.height();
        let top = $target.position().top;
        let marginTop = parseInt($target.css('margin-top'), 10);
        let paddingTop = parseInt($target.css('padding-top'), 10);
        let marginBottom = parseInt($target.css('margin-bottom'), 10);
        let paddingBottom = parseInt($target.css('padding-bottom'), 10);

        // 计算初步的结果
        let resultHeight = height + paddingTop + marginTop + paddingBottom + marginBottom;
        let resultTop = top + menuContainer.height();

        // let spaceValue;

        // // 判断是否超出下边界
        // spaceValue = (resultTop + resultHeight) - (txtTop + txtHeight);
        // if (spaceValue > 0) {
        //     resultHeight = resultHeight - spaceValue;
        // }

        // // 判断是否超出了下边界
        // spaceValue = txtTop > resultTop;
        // if (spaceValue) {
        //     resultHeight = resultHeight - spaceValue;
        //     top = top + spaceValue;
        // }

        // 按照最终结果渲染
        $tip.css({
          height: height + paddingTop + marginTop + paddingBottom + marginBottom,
          top: top + menuContainer.height()
        });
      }

      function removeTip() {
        if (!isTipInTxt) {
          return;
        }
        $tip.remove();
        isTipInTxt = false;
      }

      $txt.on('mouseenter', 'ul,ol,blockquote,p,h1,h2,h3,h4,h5,table,pre', function (e) {
        addAndShowTip($(e.currentTarget));
      }).on('mouseleave', function () {
        removeTip();
      });
    };

  });
// 工具函数
  _e(function (E, $) {

    // IE8 [].indexOf()
    if (!Array.prototype.indexOf) {
      //IE低版本不支持 arr.indexOf
      Array.prototype.indexOf = function (elem) {
        let i = 0,
          length = this.length;
        for (; i < length; i++) {
          if (this[i] === elem) {
            return i;
          }
        }
        return -1;
      };
      //IE低版本不支持 arr.lastIndexOf
      Array.prototype.lastIndexOf = function (elem) {
        let length = this.length;
        for (length = length - 1; length >= 0; length--) {
          if (this[length] === elem) {
            return length;
          }
        }
        return -1;
      };
    }

    // IE8 Date.now()
    if (!Date.now) {
      Date.now = function () {
        return new Date().valueOf();
      };
    }

    // console.knock && console.warn && console.error
    let console = window.console;
    let emptyFn = function () {
    };
    $.each(['info', 'log', 'warn', 'error'], function (key, value) {
      if (console == null) {
        E[value] = emptyFn;
      } else {
        E[value] = function (info) {
          // 通过配置来控制打印输出
          if (E.config && E.config.printLog) {
            console[value]('wangEditor提示: ' + info);
          }
        };
      }
    });

    // 获取随机数
    E.random = function () {
      return Math.random().toString().slice(2);
    };

    // 浏览器是否支持 placeholder
    E.placeholder = 'placeholder' in document.createElement('input');

    // 兼容IE8的 input placeholder
    E.placeholderForIE8 = function ($container) {
      if (E.placeholder) {
        return;
      }
      $container.find('input[placeholder]').each(function () {
        let $input = $(this);
        let placeholder = $input.attr('placeholder');

        if ($input.val() === '') {
          $input.css('color', '#666');
          $input.val(placeholder);

          $input.on('focus.placeholder click.placeholder', function () {
            $input.val('');
            $input.css('color', '#333');
            $input.off('focus.placeholder click.placeholder');
          });
        }
      });
    };
  });
// 语言包
  _e(function (E, $) {
    E.langs = {};

    // 中文
    E.langs['zh-cn'] = {
      bold: '粗体',
      underline: '下划线',
      italic: '斜体',
      forecolor: '文字颜色',
      bgcolor: '背景色',
      strikethrough: '删除线',
      eraser: '清空格式',
      source: '源码',
      quote: '引用',
      fontfamily: '字体',
      fontsize: '字号',
      head: '标题',
      orderlist: '有序列表',
      unorderlist: '无序列表',
      alignleft: '左对齐',
      aligncenter: '居中',
      alignright: '右对齐',
      link: '链接',
      text: '文本',
      submit: '提交',
      cancel: '取消',
      unlink: '取消链接',
      table: '表格',
      emotion: '表情',
      img: '图片',
      video: '视频',
      'width': '宽',
      'height': '高',
      location: '位置',
      loading: '加载中',
      searchlocation: '搜索位置',
      dynamicMap: '动态地图',
      clearLocation: '清除位置',
      langDynamicOneLocation: '动态地图只能显示一个位置',
      insertcode: '插入代码',
      undo: '撤销',
      redo: '重复',
      fullscreen: '全屏',
      openLink: '打开链接'
    };

    // 英文
    E.langs.en = {
      bold: 'Bold',
      underline: 'Underline',
      italic: 'Italic',
      forecolor: 'Color',
      bgcolor: 'Backcolor',
      strikethrough: 'Strikethrough',
      eraser: 'Eraser',
      source: 'Codeview',
      quote: 'Quote',
      fontfamily: 'Font family',
      fontsize: 'Font size',
      head: 'Head',
      orderlist: 'Ordered list',
      unorderlist: 'Unordered list',
      alignleft: 'Align left',
      aligncenter: 'Align center',
      alignright: 'Align right',
      link: 'Insert link',
      text: 'Text',
      submit: 'Submit',
      cancel: 'Cancel',
      unlink: 'Unlink',
      table: 'Table',
      emotion: 'Emotions',
      img: 'Image',
      video: 'Video',
      'width': 'width',
      'height': 'height',
      location: 'Location',
      loading: 'Loading',
      searchlocation: 'search',
      dynamicMap: 'Dynamic',
      clearLocation: 'Clear',
      langDynamicOneLocation: 'Only one location in dynamic map',
      insertcode: 'Insert Code',
      undo: 'Undo',
      redo: 'Redo',
      fullscreen: 'Full screnn',
      openLink: 'open link'
    };
  });
// 全局配置
  _e(function (E, $) {

    E.config = {};

    // 全屏时的 z-index
    E.config.zindex = 10000;

    // 是否打印log
    E.config.printLog = true;

    // 菜单吸顶：false - 不吸顶；number - 吸顶，值为top值
    E.config.menuFixed = 0;

    // 编辑源码时，过滤 javascript
    E.config.jsFilter = true;

    // 编辑器允许的标签
    E.config.legalTags = 'p,h1,h2,h3,h4,h5,h6,blockquote,table,ul,ol,pre';

    // 语言包
    E.config.lang = E.langs['zh-cn'];

    // 菜单配置
    E.config.menus = [
      'source',
      '|',
      'bold',
      'underline',
      'italic',
      'strikethrough',
      'eraser',
      'forecolor',
      'bgcolor',
      '|',
      'quote',
      'fontfamily',
      'fontsize',
      'head',
      'unorderlist',
      'orderlist',
      'alignleft',
      'aligncenter',
      'alignright',
      '|',
      'link',
      'unlink',
      'table',
      'emotion',
      '|',
      'img',
      'video',
      'location',
      'insertcode',
      '|',
      'undo',
      'redo',
      'fullscreen'
    ];

    // 颜色配置
    E.config.colors = {
      // 'value': 'title'
      '#880000': '暗红色',
      '#800080': '紫色',
      '#C81E4A': '设计学院红',
      '#ff0000': '红色',
      '#ff00ff': '鲜粉色',
      '#000080': '深蓝色',
      '#0000ff': '蓝色',
      '#00ffff': '湖蓝色',
      '#008080': '蓝绿色',
      '#008000': '绿色',
      '#808000': '橄榄色',
      '#00ff00': '浅绿色',
      '#ffcc00': '橙黄色',
      '#656464': '深灰色',
      '#808080': '灰色',
      '#c0c0c0': '银色',
      '#000000': '黑色',
      '#ffffff': '白色'
    };

    // 字体
    E.config.familys = [
      '宋体', '黑体', '楷体', '微软雅黑',
      'Arial', 'Verdana', 'Georgia',
      'Times New Roman', 'Microsoft JhengHei',
      'Trebuchet MS', 'Courier New', 'Impact', 'Comic Sans MS', 'Consolas'
    ];

    // 字号
    E.config.fontsizes = {
      // 格式：'value': 'title'
      1: '12px',
      2: '13px',
      3: '16px',
      4: '18px',
      5: '24px',
      6: '32px',
      7: '48px'
    };

    // 表情包
    E.config.emotionsShow = 'icon'; // 显示项，默认为'icon'，也可以配置成'value'
    E.config.emotions = {
      // 'default': {
      //     title: '默认',
      //     data: './emotions.data'
      // },
      'weibo': {
        title: '微博表情',
        data: [
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/7a/shenshou_thumb.gif',
            value: '[草泥马]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/60/horse2_thumb.gif',
            value: '[神马]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/fuyun_thumb.gif',
            value: '[浮云]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/c9/geili_thumb.gif',
            value: '[给力]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/f2/wg_thumb.gif',
            value: '[围观]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/70/vw_thumb.gif',
            value: '[威武]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/6e/panda_thumb.gif',
            value: '[熊猫]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/81/rabbit_thumb.gif',
            value: '[兔子]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/otm_thumb.gif',
            value: '[奥特曼]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/15/j_thumb.gif',
            value: '[囧]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/89/hufen_thumb.gif',
            value: '[互粉]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/c4/liwu_thumb.gif',
            value: '[礼物]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/ac/smilea_thumb.gif',
            value: '[呵呵]'
          },
          {
            icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/0b/tootha_thumb.gif',
            value: '[哈哈]'
          }
        ]
      }
    };

    // 百度地图的key
    E.config.mapAk = 'TVhjYjq1ICT2qqL5LdS8mwas';

    // 上传图片的配置
    // server地址
    E.config.uploadImgUrl = '';
    // 超时时间
    E.config.uploadTimeout = 20 * 1000;
    // 用于存储上传回调事件
    E.config.uploadImgFns = {};
    // 自定义上传图片的filename
    // E.config.uploadImgFileName = 'customFileName';

    // 自定义上传，设置为 true 之后，显示上传图标
    E.config.customUpload = false;
    // 自定义上传的init事件
    // E.config.customUploadInit = function () {....};

    // 自定义上传时传递的参数（如 token）
    E.config.uploadParams = {
      /* token: 'abcdef12345' */
    };

    // 自定义上传是的header参数
    E.config.uploadHeaders = {
      /* 'Accept' : 'text/x-json' */
    };

    // 隐藏网络图片，默认为 false
    E.config.hideLinkImg = false;

    // 是否过滤粘贴内容
    E.config.pasteFilter = true;

    // 是否粘贴纯文本，当 editor.config.pasteFilter === false 时候，此配置将失效
    E.config.pasteText = false;

    // 插入代码时，默认的语言
    E.config.codeDefaultLang = 'javascript';

  });
// 全局UI
  _e(function (E, $) {

    E.UI = {};

    // 为菜单自定义配置的UI
    E.UI.menus = {
      // 这个 default 不加引号，在 IE8 会报错
      'default': {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-command"></i></a>',
        selected: '.selected'
      },
      bold: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-bold"></i></a>',
        selected: '.selected'
      },
      underline: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-underline"></i></a>',
        selected: '.selected'
      },
      italic: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-italic"></i></a>',
        selected: '.selected'
      },
      forecolor: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-pencil"></i></a>',
        selected: '.selected'
      },
      bgcolor: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-brush"></i></a>',
        selected: '.selected'
      },
      strikethrough: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-strikethrough"></i></a>',
        selected: '.selected'
      },
      eraser: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-eraser"></i></a>',
        selected: '.selected'
      },
      quote: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-quotes-left"></i></a>',
        selected: '.selected'
      },
      source: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-code"></i></a>',
        selected: '.selected'
      },
      fontfamily: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-font2"></i></a>',
        selected: '.selected'
      },
      fontsize: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-text-height"></i></a>',
        selected: '.selected'
      },
      head: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-header"></i></a>',
        selected: '.selected'
      },
      orderlist: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-list-numbered"></i></a>',
        selected: '.selected'
      },
      unorderlist: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-list-bullet"></i></a>',
        selected: '.selected'
      },
      alignleft: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-align-left"></i></a>',
        selected: '.selected'
      },
      aligncenter: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-align-center"></i></a>',
        selected: '.selected'
      },
      alignright: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-align-right"></i></a>',
        selected: '.selected'
      },
      link: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-link"></i></a>',
        selected: '.selected'
      },
      unlink: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-unlink"></i></a>',
        selected: '.selected'
      },
      table: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-table"></i></a>',
        selected: '.selected'
      },
      emotion: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-happy"></i></a>',
        selected: '.selected'
      },
      img: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-picture"></i></a>',
        selected: '.selected'
      },
      video: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-play"></i></a>',
        selected: '.selected'
      },
      location: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-location"></i></a>',
        selected: '.selected'
      },
      insertcode: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-terminal"></i></a>',
        selected: '.selected'
      },
      undo: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-ccw"></i></a>',
        selected: '.selected'
      },
      redo: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-cw"></i></a>',
        selected: '.selected'
      },
      fullscreen: {
        normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-enlarge2"></i></a>',
        selected: '<a href="#" tabindex="-1" class="selected"><i class="wangeditor-menu-img-shrink2"></i></a>'
      }
    };

  });
// 对象配置
  _e(function (E, $) {

    E.fn.initDefaultConfig = function () {
      let editor = this;
      editor.config = $.extend({}, E.config);
      editor.UI = $.extend({}, E.UI);
    };

  });
// 增加 container
  _e(function (E, $) {

    E.fn.addEditorContainer = function () {
      this.$editorContainer = $('<div class="wangEditor-container"></div>');
    };

  });
// 增加编辑区域对象
  _e(function (E, $) {

    E.fn.addTxt = function () {
      let editor = this;
      let txt = new E.Txt(editor);

      editor.txt = txt;
    };

  });
// 增加menuContainer对象
  _e(function (E, $) {

    E.fn.addMenuContainer = function () {
      let editor = this;
      editor.menuContainer = new E.MenuContainer(editor);
    };

  });
// 增加menus
  _e(function (E, $) {

    // 存储创建菜单的函数
    E.createMenuFns = [];
    E.createMenu = function (fn) {
      E.createMenuFns.push(fn);
    };

    // 创建所有菜单
    E.fn.addMenus = function () {
      let editor = this;
      let menuIds = editor.config.menus;

      // 检验 menuId 是否在配置中存在
      function check(menuId) {
        if (menuIds.indexOf(menuId) >= 0) {
          return true;
        }
        return false;
      }

      // 遍历所有的菜单创建函数，并执行
      $.each(E.createMenuFns, function (k, createMenuFn) {
        createMenuFn.call(editor, check);
      });
    };

  });
// bold菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'bold';
      if (!check(menuId)) {
        return;
      }

      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.bold,
        commandName: 'Bold'
      });

      // 定义选中状态下的click事件
      menu.clickEventSelected = function (e) {
        let isRangeEmpty = editor.isRangeEmpty();
        if (!isRangeEmpty) {
          // 如果选区有内容，则执行基础命令
          editor.command(e, 'Bold');
        } else {
          // 如果选区没有内容
          editor.commandForElem('b,strong,h1,h2,h3,h4,h5', e, 'Bold');
        }
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// underline菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'underline';
      if (!check(menuId)) {
        return;
      }

      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.underline,
        commandName: 'Underline'
      });

      // 定义选中状态下的click事件
      menu.clickEventSelected = function (e) {
        let isRangeEmpty = editor.isRangeEmpty();
        if (!isRangeEmpty) {
          // 如果选区有内容，则执行基础命令
          editor.command(e, 'Underline');
        } else {
          // 如果选区没有内容
          editor.commandForElem('u,a', e, 'Underline');
        }
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// italic 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'italic';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.italic,
        commandName: 'Italic'
      });

      // 定义选中状态下的click事件
      menu.clickEventSelected = function (e) {
        let isRangeEmpty = editor.isRangeEmpty();
        if (!isRangeEmpty) {
          // 如果选区有内容，则执行基础命令
          editor.command(e, 'Italic');
        } else {
          // 如果选区没有内容
          editor.commandForElem('i', e, 'Italic');
        }
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// forecolor 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'forecolor';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;
      let configColors = editor.config.colors;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.forecolor
      });

      // 创建 dropPanel
      let $content = $('<div></div>');
      $.each(configColors, function (k, v) {
        $content.append(
          [
            '<a href="#" class="color-item"',
            '    title="' + v + '" commandValue="' + k + '" ',
            '    style="color: ' + k + '" ',
            '><i class="wangeditor-menu-img-pencil"></i></a>'
          ].join('')
        );
      });
      $content.on('click', 'a[commandValue]', function (e) {
        // 执行命令
        let $elem = $(this);
        let commandValue = $elem.attr('commandValue');

        if (menu.selected && editor.isRangeEmpty()) {
          // 当前处于选中状态，并且选中内容为空
          editor.commandForElem('font[color]', e, 'forecolor', commandValue);
        } else {
          // 当前未处于选中状态，或者有选中内容。则执行默认命令
          editor.command(e, 'forecolor', commandValue);
        }
      });
      menu.dropPanel = new E.DropPanel(editor, menu, {
        $content: $content,
        width: 125
      });

      // 定义 update selected 事件
      menu.updateSelectedEvent = function () {
        let rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'font[color]');
        if (rangeElem) {
          return true;
        }
        return false;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// bgcolor 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'bgcolor';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;
      let configColors = editor.config.colors;

      // 检查元素是否有 background-color: 内联样式
      function checkElemFn(elem) {
        let cssText;
        if (elem && elem.style && elem.style.cssText != null) {
          cssText = elem.style.cssText;
          if (cssText && cssText.indexOf('background-color:') >= 0) {
            return true;
          }
        }
        return false;
      }

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.bgcolor
      });

      // 创建 dropPanel
      let $content = $('<div></div>');
      $.each(configColors, function (k, v) {
        $content.append(
          [
            '<a href="#" class="color-item"',
            '    title="' + v + '" commandValue="' + k + '" ',
            '    style="color: ' + k + '" ',
            '><i class="wangeditor-menu-img-brush"></i></a>'
          ].join('')
        );
      });
      $content.on('click', 'a[commandValue]', function (e) {
        // 执行命令

        let $elem = $(this);
        let commandValue = $elem.attr('commandValue');

        if (menu.selected && editor.isRangeEmpty()) {
          // 当前处于选中状态，并且选中内容为空。使用 commandForElem 执行命令
          editor.commandForElem({
            selector: 'span,font',
            check: checkElemFn
          }, e, 'BackColor', commandValue);
        } else {
          // 当前未处于选中状态，或者有选中内容。则执行默认命令
          editor.command(e, 'BackColor', commandValue);
        }
      });
      menu.dropPanel = new E.DropPanel(editor, menu, {
        $content: $content,
        width: 125
      });

      // 定义 update selected 事件
      menu.updateSelectedEvent = function () {
        let rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'span,font', checkElemFn);

        if (rangeElem) {
          return true;
        }
        return false;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// strikethrough 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'strikethrough';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.strikethrough,
        commandName: 'StrikeThrough'
      });

      // 定义选中状态下的click事件
      menu.clickEventSelected = function (e) {
        let isRangeEmpty = editor.isRangeEmpty();
        if (!isRangeEmpty) {
          // 如果选区有内容，则执行基础命令
          editor.command(e, 'StrikeThrough');
        } else {
          // 如果选区没有内容
          editor.commandForElem('strike', e, 'StrikeThrough');
        }
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// eraser 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'eraser';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.eraser,
        commandName: 'RemoveFormat'
      });

      // 定义点击事件
      menu.clickEvent = function (e) {
        let isRangeEmpty = editor.isRangeEmpty();

        if (!isRangeEmpty) {
          // 选区不是空的，则执行默认命令
          editor.command(e, 'RemoveFormat');
          return;
        }

        let $clearElem;

        // 自定义的命令函数
        function commandFn() {
          let editor = this;
          let rangeElem;
          let pElem, $pElem;
          let quoteElem, $quoteElem;
          let listElem, $listElem;

          // 获取选区 elem
          rangeElem = editor.getRangeElem();
          // 第一步，获取 quote 父元素
          quoteElem = editor.getSelfOrParentByName(rangeElem, 'blockquote');
          if (quoteElem) {
            $quoteElem = $(quoteElem);
            $clearElem = $('<p>' + $quoteElem.text() + '</p>');
            $quoteElem.after($clearElem).remove();
          }
          // 第二步，获取 p h 父元素
          pElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5');
          if (pElem) {
            $pElem = $(pElem);
            $clearElem = $('<p>' + $pElem.text() + '</p>');
            $pElem.after($clearElem).remove();
          }
          // 第三步，获取list
          listElem = editor.getSelfOrParentByName(rangeElem, 'ul,ol');
          if (listElem) {
            $listElem = $(listElem);
            $clearElem = $('<p>' + $listElem.text() + '</p>');
            $listElem.after($clearElem).remove();
          }
        }

        // 自定义 callback 事件
        function callback() {
          // callback中，设置range为clearElem
          let editor = this;
          if ($clearElem) {
            editor.restoreSelectionByElem($clearElem.get(0));
          }
        }

        // 执行自定义命令
        editor.customCommand(e, commandFn, callback);
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// source 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'source';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;
      let txtHtml;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.source
      });

      menu.isShowCode = false;

      // 更新内容
      function updateValue() {
        let $code = menu.$codeTextarea;
        let $txt = editor.txt.$txt;
        let value = $.trim($code.val()); // 取值

        if (!value) {
          value = '<p><br></p>';
        }

        // 过滤js代码
        if (editor.config.jsFilter) {

          value = value.replace(/<script[\s\S]*?<\/script>/ig, '');
        }
        // 赋值
        try {
          $txt.html(value);
        } catch (ex) {
          // 更新 html 源码出错，一般都是取消了 js 过滤之后，js报错导致的
        }
      }

      // 定义click事件
      menu.clickEvent = function (e) {
        let self = this;
        let editor = self.editor;
        let $txt = editor.txt.$txt;
        let txtOuterHeight = $txt.outerHeight();
        let txtHeight = $txt.height();

        if (!self.$codeTextarea) {
          self.$codeTextarea = $('<textarea class="code-textarea"></textarea>');
        }
        let $code = self.$codeTextarea;
        $code.css({
          height: txtHeight,
          'margin-top': txtOuterHeight - txtHeight
        });

        // 赋值
        $code.val($txt.html());

        // 监控变化
        $code.on('change', function (e) {
          updateValue();
        });

        // 渲染
        $txt.after($code).hide();
        $code.show();

        // 更新状态
        menu.isShowCode = true;

        // 执行 updateSelected 事件
        this.updateSelected();

        // 禁用其他菜单
        editor.disableMenusExcept('source');

        // 记录当前html值
        txtHtml = $txt.html();
      };

      // 定义选中状态下的click事件
      menu.clickEventSelected = function (e) {
        let self = this;
        let editor = self.editor;
        let $txt = editor.txt.$txt;
        let $code = self.$codeTextarea;
        let value;

        if (!$code) {
          return;
        }

        // 更新内容
        updateValue();

        // 渲染
        $code.after($txt).hide();
        $txt.show();

        // 更新状态
        menu.isShowCode = false;

        // 执行 updateSelected 事件
        this.updateSelected();

        // 启用其他菜单
        editor.enableMenusExcept('source');

        // 判断是否执行 onchange 事件
        if ($txt.html() !== txtHtml) {
          if (editor.onchange && typeof editor.onchange === 'function') {
            editor.onchange.call(editor);
          }
        }
      };

      // 定义切换选中状态事件
      menu.updateSelectedEvent = function () {
        return this.isShowCode;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// quote 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'quote';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.quote,
        commandName: 'formatBlock',
        commandValue: 'blockquote'
      });

      // 定义click事件
      menu.clickEvent = function (e) {
        let rangeElem = editor.getRangeElem();
        let $rangeElem;
        if (!rangeElem) {
          e.preventDefault();
          return;
        }
        let currentQuote = editor.getSelfOrParentByName(rangeElem, 'blockquote');
        let $quote;

        if (currentQuote) {
          // 说明当前在quote之内，不做任何处理
          e.preventDefault();
          return;
        }

        rangeElem = editor.getLegalTags(rangeElem);
        $rangeElem = $(rangeElem);

        // 无文字，则不允许执行引用
        if (!$rangeElem.text()) {
          return;
        }


        if (!rangeElem) {
          // 执行默认命令
          // IE8 下执行此处（不过，经测试代码无效，也不报错）
          editor.command(e, 'formatBlock', 'blockquote');
          return;
        }

        // 自定义command事件
        function commandFn() {
          $quote = $('<p>' + $rangeElem.text() + '</p>');
          $rangeElem.after($quote).remove();
          $quote.wrap('<blockquote>');
        }

        // 自定义 callback 事件
        function callback() {
          // callback中，设置range为quote
          let editor = this;
          if ($quote) {
            editor.restoreSelectionByElem($quote.get(0));
          }
        }

        // 执行自定义命令
        editor.customCommand(e, commandFn, callback);
      };

      // 定义选中状态下的click事件
      menu.clickEventSelected = function (e) {
        let rangeElem;
        let quoteElem;
        let $lastChild;

        // 获取当前选区的elem，并试图往上找 quote 元素
        rangeElem = editor.getRangeElem();
        quoteElem = editor.getSelfOrParentByName(rangeElem, 'blockquote');
        if (!quoteElem) {
          // 没找到，则返回
          e.preventDefault();
          return;
        }

        // 自定义的command事件
        function commandFn() {
          let $quoteElem;
          let $children;

          $quoteElem = $(quoteElem);
          $children = $quoteElem.children();
          if ($children.length) {
            $children.each(function (k) {
              let $item = $(this);
              if ($item.get(0).nodeName === 'P') {
                $quoteElem.after($item);
              } else {
                $quoteElem.after('<p>' + $item.text() + '</p>');
              }
              $lastChild = $item;  // 记录最后一个子元素，用于callback中的range定位
            });
            $quoteElem.remove();
            return;
          }
        }

        // 自定义的callback函数
        function callback() {
          // callback中，设置range为lastChild
          let editor = this;
          if ($lastChild) {
            editor.restoreSelectionByElem($lastChild.get(0));
          }
        }

        // 执行自定义命令
        editor.customCommand(e, commandFn, callback);
      };

      // 定义更新选中状态的事件
      menu.updateSelectedEvent = function () {
        let self = this; //菜单对象
        let editor = self.editor;
        let rangeElem;

        rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'blockquote');

        if (rangeElem) {
          return true;
        }

        return false;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;

      // --------------- 两次点击 enter 跳出引用 ---------------
      editor.ready(function () {
        let editor = this;
        let $txt = editor.txt.$txt;
        let isPrevEnter = false;  // 是不是刚刚在quote中按了 enter 键
        $txt.on('keydown', function (e) {
          if (e.keyCode !== 13) {
            // 不是 enter 键
            isPrevEnter = false;
            return;
          }

          let rangeElem = editor.getRangeElem();
          rangeElem = editor.getSelfOrParentByName(rangeElem, 'blockquote');
          if (!rangeElem) {
            // 选区不是 quote
            isPrevEnter = false;
            return;
          }

          if (!isPrevEnter) {
            // 最近没有在qote中按enter键
            isPrevEnter = true;
            return;
          }

          let currentRangeElem = editor.getRangeElem();
          let $currentRangeElem = $(currentRangeElem);
          if ($currentRangeElem.length) {
            $currentRangeElem.parent().after($currentRangeElem);
          }

          // 设置选区
          editor.restoreSelectionByElem(currentRangeElem, 'start');

          isPrevEnter = false;
          // 阻止默认行文
          e.preventDefault();

        });
      }); // editor.ready(

      // --------------- 处理quote中无内容时不能删除的问题 ---------------
      editor.ready(function () {
        let editor = this;
        let $txt = editor.txt.$txt;
        let $rangeElem;

        function commandFn() {
          $rangeElem && $rangeElem.remove();
        }

        function callback() {
          if (!$rangeElem) {
            return;
          }
          let $prev = $rangeElem.prev();
          if ($prev.length) {
            // 有 prev 则定位到 prev 最后
            editor.restoreSelectionByElem($prev.get(0));
          } else {
            // 无 prev 则初始化选区
            editor.initSelection();
          }
        }

        $txt.on('keydown', function (e) {
          if (e.keyCode !== 8) {
            // 不是 backspace 键
            return;
          }

          let rangeElem = editor.getRangeElem();
          rangeElem = editor.getSelfOrParentByName(rangeElem, 'blockquote');
          if (!rangeElem) {
            // 选区不是 quote
            return;
          }
          $rangeElem = $(rangeElem);

          let text = $rangeElem.text();
          if (text) {
            // quote 中还有内容
            return;
          }
          editor.customCommand(e, commandFn, callback);

        }); // $txt.on
      }); // editor.ready(
    });

  });
// 字体 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'fontfamily';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;
      let configFamilys = editor.config.familys;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.fontfamily,
        commandName: 'fontName'
      });

      // 初始化数据
      let data = {};
      /*
			 data 需要的结构
			 {
			 'commandValue': 'title'
			 ...
			 }
			 */
      $.each(configFamilys, function (k, v) {
        // configFamilys 是数组，data 是对象
        data[v] = v;
      });

      // 创建droplist
      let tpl = '<span style="font-family:{#commandValue};">{#title}</span>';
      menu.dropList = new E.DropList(editor, menu, {
        data: data,
        tpl: tpl,
        selectorForELemCommand: 'font[face]'  // 为了执行 editor.commandForElem 而传入的elem查询方式
      });

      // 定义 update selected 事件
      menu.updateSelectedEvent = function () {
        let rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'font[face]');
        if (rangeElem) {
          return true;
        }
        return false;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });
  });
// 字号 菜单
  _e(function (E, $) {
    E.createMenu(function (check) {
      let menuId = 'fontsize';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;
      let configSize = editor.config.fontsizes;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.fontsize,
        commandName: 'fontSize'
      });

      // 初始化数据
      let data = configSize;
      /*
			 data 需要的结构
			 {
			 'commandValue': 'title'
			 ...
			 }
			 */

      // 创建droplist
      let tpl = '<span style="font-size:{#title};">{#title}</span>';
      menu.dropList = new E.DropList(editor, menu, {
        data: data,
        tpl: tpl,
        selectorForELemCommand: 'font[size]'  // 为了执行 editor.commandForElem 而传入的elem查询方式
      });

      // 定义 update selected 事件
      menu.updateSelectedEvent = function () {
        let rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'font[size]');
        if (rangeElem) {
          return true;
        }
        return false;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });
  });
// head 菜单
  _e(function (E, $) {
    E.createMenu(function (check) {
      let menuId = 'head';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.head,
        commandName: 'formatBlock'
      });

      // 初始化数据
      let data = {
        '<h1>': '标题1',
        '<h2>': '标题2',
        '<h3>': '标题3',
        '<h4>': '标题4',
        '<h5>': '标题5'
      };
      /*
			 data 需要的结构
			 {
			 'commandValue': 'title'
			 ...
			 }
			 */

      let isOrderedList;

      function beforeEvent(e) {
        if (editor.queryCommandState('InsertOrderedList')) {
          isOrderedList = true;

          // 先取消有序列表
          editor.command(e, 'InsertOrderedList');
        } else {
          isOrderedList = false;
        }
      }

      function afterEvent(e) {
        if (isOrderedList) {
          // 再设置有序列表
          editor.command(e, 'InsertOrderedList');
        }
      }

      // 创建droplist
      let tpl = '{#commandValue}{#title}';
      menu.dropList = new E.DropList(editor, menu, {
        data: data,
        tpl: tpl,
        // 对 ol 直接设置 head，会出现每个 li 的 index 都变成 1 的问题，因此要先取消 ol，然后设置 head，最后再增加上 ol
        beforeEvent: beforeEvent,
        afterEvent: afterEvent
      });

      // 定义 update selected 事件
      menu.updateSelectedEvent = function () {
        let rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'h1,h2,h3,h4,h5');
        if (rangeElem) {
          return true;
        }
        return false;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });
  });
// unorderlist 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'unorderlist';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.unorderlist,
        commandName: 'InsertUnorderedList'
      });

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// orderlist 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'orderlist';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.orderlist,
        commandName: 'InsertOrderedList'
      });

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// alignleft 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'alignleft';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.alignleft,
        commandName: 'JustifyLeft'
      });

      // 定义 update selected 事件
      menu.updateSelectedEvent = function () {
        let rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5,li', function (elem) {
          let cssText;
          if (elem && elem.style && elem.style.cssText != null) {
            cssText = elem.style.cssText;
            if (cssText && /text-align:\s*left;/.test(cssText)) {
              return true;
            }
          }
          if ($(elem).attr('align') === 'left') {
            // ff 中，设置align-left之后，会是 <p align="left">xxx</p>
            return true;
          }
          return false;
        });
        if (rangeElem) {
          return true;
        }
        return false;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// aligncenter 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'aligncenter';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.aligncenter,
        commandName: 'JustifyCenter'
      });

      // 定义 update selected 事件
      menu.updateSelectedEvent = function () {
        let rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5,li', function (elem) {
          let cssText;
          if (elem && elem.style && elem.style.cssText != null) {
            cssText = elem.style.cssText;
            if (cssText && /text-align:\s*center;/.test(cssText)) {
              return true;
            }
          }
          if ($(elem).attr('align') === 'center') {
            // ff 中，设置align-center之后，会是 <p align="center">xxx</p>
            return true;
          }
          return false;
        });
        if (rangeElem) {
          return true;
        }
        return false;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// alignright 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'alignright';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.alignright,
        commandName: 'JustifyRight'
      });

      // 定义 update selected 事件
      menu.updateSelectedEvent = function () {
        let rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5,li', function (elem) {
          let cssText;
          if (elem && elem.style && elem.style.cssText != null) {
            cssText = elem.style.cssText;
            if (cssText && /text-align:\s*right;/.test(cssText)) {
              return true;
            }
          }
          if ($(elem).attr('align') === 'right') {
            // ff 中，设置align-right之后，会是 <p align="right">xxx</p>
            return true;
          }
          return false;
        });
        if (rangeElem) {
          return true;
        }
        return false;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// link 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'link';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.link
      });

      // 创建 dropPanel
      let $content = $('<div></div>');
      let $div1 = $('<div style="margin:20px 10px;" class="clearfix"></div>');
      let $div2 = $div1.clone();
      let $div3 = $div1.clone().css('margin', '0 10px');
      let $textInput = $('<input type="text" class="block" placeholder="' + lang.text + '"/>');
      let $urlInput = $('<input type="text" class="block" placeholder="' + lang.link + '"/>');
      let $btnSubmit = $('<button class="right">' + lang.submit + '</button>');
      let $btnCancel = $('<button class="right gray">' + lang.cancel + '</button>');

      $div1.append($textInput);
      $div2.append($urlInput);
      $div3.append($btnSubmit).append($btnCancel);
      $content.append($div1).append($div2).append($div3);

      menu.dropPanel = new E.DropPanel(editor, menu, {
        $content: $content,
        width: 300
      });

      // 定义click事件
      menu.clickEvent = function (e) {
        let menu = this;
        let dropPanel = menu.dropPanel;

        // -------------隐藏----------------
        if (dropPanel.isShowing) {
          dropPanel.hide();
          return;
        }

        // -------------显示----------------

        // 重置 input
        $textInput.val('');
        $urlInput.val('http://');

        // 获取url
        let url = '';
        let rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'a');
        if (rangeElem) {
          url = rangeElem.href || '';
        }

        // 获取 text
        let text = '';
        let isRangeEmpty = editor.isRangeEmpty();
        if (!isRangeEmpty) {
          // 选区不是空
          text = editor.getRangeText() || '';
        } else if (rangeElem) {
          // 如果选区空，并且在 a 标签之内
          text = rangeElem.textContent || rangeElem.innerHTML;
        }

        // 设置 url 和 text
        url && $urlInput.val(url);
        text && $textInput.val(text);

        // 如果有选区内容，textinput 不能修改
        if (!isRangeEmpty) {
          $textInput.attr('disabled', true);
        } else {
          $textInput.removeAttr('disabled');
        }

        // 显示（要设置好了所有input的值和属性之后再显示）
        dropPanel.show();
      };

      // 定义 update selected 事件
      menu.updateSelectedEvent = function () {
        let rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'a');
        if (rangeElem) {
          return true;
        }
        return false;
      };

      // 『取消』 按钮
      $btnCancel.click(function (e) {
        e.preventDefault();
        menu.dropPanel.hide();
      });

      // 『确定』按钮
      $btnSubmit.click(function (e) {
        e.preventDefault();
        let rangeElem = editor.getRangeElem();
        let targetElem = editor.getSelfOrParentByName(rangeElem, 'a');
        let isRangeEmpty = editor.isRangeEmpty();

        let $linkElem, linkHtml;
        let commandFn, callback;
        let $txt = editor.txt.$txt;
        let $oldLinks, $newLinks;
        let uniqId = 'link' + E.random();

        // 获取数据
        let url = $.trim($urlInput.val());
        let text = $.trim($textInput.val());

        if (!url) {
          menu.dropPanel.focusFirstInput();
          return;
        }
        if (!text) {
          text = url;
        }

        if (!isRangeEmpty) {
          // 选中区域有内容，则执行默认命令

          // 获取目前 txt 内所有链接，并为当前链接做一个标记
          $oldLinks = $txt.find('a');
          $oldLinks.attr(uniqId, '1');

          // 执行命令
          editor.command(e, 'createLink', url);

          // 去的没有标记的链接，即刚刚插入的链接
          $newLinks = $txt.find('a').not('[' + uniqId + ']');
          $newLinks.attr('target', '_blank'); // 增加 _blank

          // 去掉之前做的标记
          $oldLinks.removeAttr(uniqId);

        } else if (targetElem) {
          // 无选中区域，在 a 标签之内，修改该 a 标签的内容和链接
          $linkElem = $(targetElem);
          commandFn = function () {
            $linkElem.attr('href', url);
            $linkElem.text(text);
          };
          callback = function () {
            let editor = this;
            editor.restoreSelectionByElem(targetElem);
          };
          // 执行命令
          editor.customCommand(e, commandFn, callback);
        } else {
          // 无选中区域，不在 a 标签之内，插入新的链接

          linkHtml = '<a href="' + url + '" target="_blank">' + text + '</a>';
          if (E.userAgent.indexOf('Firefox') > 0) {
            linkHtml += '<span>&nbsp;</span>';
          }
          editor.command(e, 'insertHtml', linkHtml);
        }

      });

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// unlink 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'unlink';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.unlink,
        commandName: 'unLink'
      });

      // click 事件
      menu.clickEvent = function (e) {
        let isRangeEmpty = editor.isRangeEmpty();
        if (!isRangeEmpty) {
          // 有选中区域，或者IE8，执行默认命令
          editor.command(e, 'unLink');
          return;
        }

        // 无选中区域...

        let rangeElem = editor.getRangeElem();
        let aElem = editor.getSelfOrParentByName(rangeElem, 'a');
        if (!aElem) {
          // 不在 a 之内，返回
          e.preventDefault();
          return;
        }

        // 在 a 之内
        let $a = $(aElem);
        let $span = $('<span>' + $a.text() + '</span>');

        function commandFn() {
          $a.after($span).remove();
        }

        function callback() {
          editor.restoreSelectionByElem($span.get(0));
        }

        editor.customCommand(e, commandFn, callback);
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// table 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'table';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.table
      });

      // dropPanel 内容
      let $content = $('<div style="font-size: 14px; color: #666; text-align:right;"></div>');
      let $table = $('<table class="choose-table" style="margin-bottom:10px;margin-top:5px;">');
      let $row = $('<span>0</span>');
      let $rowspan = $('<span> 行 </span>');
      let $col = $('<span>0</span>');
      let $colspan = $('<span> 列</span>');
      let $tr;
      let i, j;

      // 创建一个n行n列的表格
      for (i = 0; i < 15; i++) {
        $tr = $('<tr index="' + (i + 1) + '">');
        for (j = 0; j < 20; j++) {
          $tr.append($('<td index="' + (j + 1) + '">'));
        }
        $table.append($tr);
      }
      $content.append($table);
      $content.append($row).append($rowspan).append($col).append($colspan);

      // 定义table事件
      $table.on('mouseenter', 'td', function (e) {
        let $currentTd = $(e.currentTarget);
        let currentTdIndex = $currentTd.attr('index');
        let $currentTr = $currentTd.parent();
        let currentTrIndex = $currentTr.attr('index');

        // 显示
        $row.text(currentTrIndex);
        $col.text(currentTdIndex);

        // 遍历设置背景颜色
        $table.find('tr').each(function () {
          let $tr = $(this);
          let trIndex = $tr.attr('index');
          if (parseInt(trIndex, 10) <= parseInt(currentTrIndex, 10)) {
            // 该行需要可能需要设置背景色
            $tr.find('td').each(function () {
              let $td = $(this);
              let tdIndex = $td.attr('index');
              if (parseInt(tdIndex, 10) <= parseInt(currentTdIndex, 10)) {
                // 需要设置背景色
                $td.addClass('active');
              } else {
                // 需要移除背景色
                $td.removeClass('active');
              }
            });
          } else {
            // 改行不需要设置背景色
            $tr.find('td').removeClass('active');
          }
        });
      }).on('mouseleave', function (e) {
        // mouseleave 删除背景色
        $table.find('td').removeClass('active');

        $row.text(0);
        $col.text(0);
      });

      // 插入表格
      $table.on('click', 'td', function (e) {
        let $currentTd = $(e.currentTarget);
        let currentTdIndex = $currentTd.attr('index');
        let $currentTr = $currentTd.parent();
        let currentTrIndex = $currentTr.attr('index');

        let rownum = parseInt(currentTrIndex, 10);
        let colnum = parseInt(currentTdIndex, 10);

        // -------- 拼接tabel html --------

        let i, j;
        let tableHtml = '<table>';
        for (i = 0; i < rownum; i++) {
          tableHtml += '<tr>';

          for (j = 0; j < colnum; j++) {
            tableHtml += '<td><span>&nbsp;</span></td>';
          }
          tableHtml += '</tr>';
        }
        tableHtml += '</table>';

        // -------- 执行命令 --------
        editor.command(e, 'insertHtml', tableHtml);
      });

      // 创建 panel
      menu.dropPanel = new E.DropPanel(editor, menu, {
        $content: $content,
        width: 262
      });

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// emotion 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'emotion';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let config = editor.config;
      let lang = config.lang;
      let configEmotions = config.emotions;
      let emotionsShow = config.emotionsShow;

      // 记录每一个表情图片的地址
      editor.emotionUrls = [];

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.emotion
      });

      // 添加表情图片的函数
      function insertEmotionImgs(data, $tabContent) {
        // 添加表情图片
        $.each(data, function (k, emotion) {
          let src = emotion.icon || emotion.url;
          let value = emotion.value || emotion.title;
          // 通过配置 editor.config.emotionsShow 的值来修改插入到编辑器的内容（图片/value）
          let commandValue = emotionsShow === 'icon' ? src : value;
          let $command = $('<a href="#" commandValue="' + commandValue + '"></a>');
          let $img = $('<img>');
          $img.attr('_src', src);  // 先将 src 复制到 '_src' 属性，先不加载

          $command.append($img);
          $tabContent.append($command);

          // 记录下每一个表情图片的地址
          editor.emotionUrls.push(src);
        });
      }

      // 拼接 dropPanel 内容
      let $panelContent = $('<div class="panel-tab"></div>');
      let $tabContainer = $('<div class="tab-container"></div>');
      let $contentContainer = $('<div class="content-container emotion-content-container"></div>');
      $.each(configEmotions, function (k, emotion) {
        let title = emotion.title;
        let data = emotion.data;

        E.log('正在处理 ' + title + ' 表情的数据...');

        // 增加该组表情的tab和content
        let $tab = $('<a href="#">' + title + ' </a>');
        $tabContainer.append($tab);
        let $tabContent = $('<div class="content"></div>');
        $contentContainer.append($tabContent);

        // tab 切换事件
        $tab.click(function (e) {
          $tabContainer.children().removeClass('selected');
          $contentContainer.children().removeClass('selected');
          $tabContent.addClass('selected');
          $tab.addClass('selected');
          e.preventDefault();
        });

        // 处理data
        if (typeof data === 'string') {
          // url 形式，需要通过ajax从该url获取数据
          E.log('将通过 ' + data + ' 地址ajax下载表情包');
          $.get(data, function (result) {
            result = $.parseJSON(result);
            E.log('下载完毕，得到 ' + result.length + ' 个表情');
            insertEmotionImgs(result, $tabContent);
          });

        } else if (Object.prototype.toString.call(data).toLowerCase().indexOf('array') > 0) {
          // 数组，即 data 直接就是表情包数据
          insertEmotionImgs(data, $tabContent);
        } else {
          // 其他情况，data格式不对
          E.error('data 数据格式错误，请修改为正确格式，参考文档：' + E.docsite);
          return;
        }
      });
      $panelContent.append($tabContainer).append($contentContainer);

      // 默认显示第一个tab
      $tabContainer.children().first().addClass('selected');
      $contentContainer.children().first().addClass('selected');

      // 插入表情command事件
      $contentContainer.on('click', 'a[commandValue]', function (e) {
        let $a = $(e.currentTarget);
        let commandValue = $a.attr('commandValue');
        let img;

        // commandValue 有可能是图片url，也有可能是表情的 value，需要区别对待

        if (emotionsShow === 'icon') {
          // 插入图片
          editor.command(e, 'InsertImage', commandValue);
        } else {
          // 插入value
          editor.command(e, 'insertHtml', '<span>' + commandValue + '</span>');
        }

        e.preventDefault();
      });

      // 添加panel
      menu.dropPanel = new E.DropPanel(editor, menu, {
        $content: $panelContent,
        width: 350
      });

      // 定义click事件（异步加载表情图片）
      menu.clickEvent = function (e) {
        let menu = this;
        let dropPanel = menu.dropPanel;

        // -------------隐藏-------------
        if (dropPanel.isShowing) {
          dropPanel.hide();
          return;
        }

        // -------------显示-------------
        dropPanel.show();

        // 异步加载图片
        if (menu.imgLoaded) {
          return;
        }
        $contentContainer.find('img').each(function () {
          let $img = $(this);
          let _src = $img.attr('_src');
          $img.on('error', function () {
            E.error('加载不出表情图片 ' + _src);
          });
          $img.attr('src', _src);
          $img.removeAttr('_src');
        });
        menu.imgLoaded = true;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// img 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'img';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.img
      });

      // 创建 panel content
      let $panelContent = $('<div class="panel-tab"></div>');
      let $tabContainer = $('<div class="tab-container"></div>');
      let $contentContainer = $('<div class="content-container"></div>');
      $panelContent.append($tabContainer).append($contentContainer);

      // tab
      let $uploadTab = $('<a href="#">上传图片</a>');
      let $linkTab = $('<a href="#">网络图片</a>');
      $tabContainer.append($uploadTab).append($linkTab);

      // 上传图片 content
      let $uploadContent = $('<div class="content"></div>');
      $contentContainer.append($uploadContent);

      // 网络图片 content
      let $linkContent = $('<div class="content"></div>');
      $contentContainer.append($linkContent);
      linkContentHandler(editor, menu, $linkContent);

      // 添加panel
      menu.dropPanel = new E.DropPanel(editor, menu, {
        $content: $panelContent,
        width: 400,
        onRender: function () {
          // 渲染后的回调事件，用于执行自定义上传的init
          // 因为渲染之后，上传面板的dom才会被渲染到页面，才能让第三方空间获取到
          let init = editor.config.customUploadInit;
          init && init.call(editor);
        }
      });

      // 增加到editor对象中
      editor.menus[menuId] = menu;

      // tab 切换事件
      function tabToggle() {
        $uploadTab.click(function (e) {
          $tabContainer.children().removeClass('selected');
          $contentContainer.children().removeClass('selected');
          $uploadContent.addClass('selected');
          $uploadTab.addClass('selected');
          e.preventDefault();
        });
        $linkTab.click(function (e) {
          $tabContainer.children().removeClass('selected');
          $contentContainer.children().removeClass('selected');
          $linkContent.addClass('selected');
          $linkTab.addClass('selected');
          e.preventDefault();

          // focus input
          if (E.placeholder) {
            $linkContent.find('input[type=text]').focus();
          }
        });

        // 默认情况
        // $uploadTab.addClass('selected');
        // $uploadContent.addClass('selected');
        $uploadTab.click();
      }

      // 隐藏上传图片
      function hideUploadImg() {
        $tabContainer.remove();
        $uploadContent.remove();
        $linkContent.addClass('selected');
      }

      // 隐藏网络图片
      function hideLinkImg() {
        $tabContainer.remove();
        $linkContent.remove();
        $uploadContent.addClass('selected');
      }

      // 判断用户是否配置了上传图片
      editor.ready(function () {
        let editor = this;
        let config = editor.config;
        let uploadImgUrl = config.uploadImgUrl;
        let customUpload = config.customUpload;
        let linkImg = config.hideLinkImg;
        let $uploadImgPanel;

        if (uploadImgUrl || customUpload) {
          // 第一，暴露出 $uploadContent 以便用户自定义 ！！！重要
          editor.$uploadContent = $uploadContent;

          // 第二，绑定tab切换事件
          tabToggle();

          if (linkImg) {
            // 隐藏网络图片
            hideLinkImg();
          }
        } else {
          // 未配置上传图片功能
          hideUploadImg();
        }

        // 点击 $uploadContent 立即隐藏 dropPanel
        // 为了兼容IE8、9的上传，因为IE8、9使用 modal 上传
        // 这里使用异步，为了不妨碍高级浏览器通过点击 $uploadContent 选择文件
        function hidePanel() {
          menu.dropPanel.hide();
        }

        $uploadContent.click(function () {
          setTimeout(hidePanel);
        });
      });
    });

    // --------------- 处理网络图片content ---------------
    function linkContentHandler(editor, menu, $linkContent) {
      let lang = editor.config.lang;
      let $urlContainer = $('<div style="margin:20px 10px 10px 10px;"></div>');
      let $urlInput = $('<input type="text" class="block" placeholder="http://"/>');
      $urlContainer.append($urlInput);
      let $btnSubmit = $('<button class="right">' + lang.submit + '</button>');
      let $btnCancel = $('<button class="right gray">' + lang.cancel + '</button>');

      $linkContent.append($urlContainer).append($btnSubmit).append($btnCancel);

      // 取消
      $btnCancel.click(function (e) {
        e.preventDefault();
        menu.dropPanel.hide();
      });

      // callback
      function callback() {
        $urlInput.val('');
      }

      // 确定
      $btnSubmit.click(function (e) {
        e.preventDefault();
        let url = $.trim($urlInput.val());
        if (!url) {
          // 无内容
          $urlInput.focus();
          return;
        }

        let imgHtml = '<img style="max-width:100%;" src="' + url + '"/>';
        editor.command(e, 'insertHtml', imgHtml, callback);
      });
    }

  });
// video 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'video';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;
      let reg = /^<(iframe)|(embed)/i;  // <iframe... 或者 <embed... 格式

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.video
      });

      // 创建 panel 内容
      let $content = $('<div></div>');
      let $linkInputContainer = $('<div style="margin:20px 10px;"></div>');
      let $linkInput = $('<input type="text" class="block" placeholder=\'格式如：<iframe src="..." frameborder=0 allowfullscreen></iframe>\'/>');
      $linkInputContainer.append($linkInput);
      let $sizeContainer = $('<div style="margin:20px 10px;"></div>');
      let $widthInput = $('<input type="text" value="640" style="width:50px;text-align:center;"/>');
      let $heightInput = $('<input type="text" value="498" style="width:50px;text-align:center;"/>');
      $sizeContainer.append('<span> ' + lang.width + ' </span>')
        .append($widthInput)
        .append('<span> px &nbsp;&nbsp;&nbsp;</span>')
        .append('<span> ' + lang.height + ' </span>')
        .append($heightInput)
        .append('<span> px </span>');
      let $btnContainer = $('<div></div>');
      let $howToCopy = $('<a href="http://www.kancloud.cn/wangfupeng/wangeditor2/134973" target="_blank" style="display:inline-block;margin-top:10px;margin-left:10px;color:#999;">如何复制视频链接？</a>');
      let $btnSubmit = $('<button class="right">' + lang.submit + '</button>');
      let $btnCancel = $('<button class="right gray">' + lang.cancel + '</button>');
      $btnContainer.append($howToCopy).append($btnSubmit).append($btnCancel);
      $content.append($linkInputContainer).append($sizeContainer).append($btnContainer);

      // 取消按钮
      $btnCancel.click(function (e) {
        e.preventDefault();
        $linkInput.val('');
        menu.dropPanel.hide();
      });

      // 确定按钮
      $btnSubmit.click(function (e) {
        e.preventDefault();
        let link = $.trim($linkInput.val());
        let $link;
        let width = parseInt($widthInput.val());
        let height = parseInt($heightInput.val());
        let $div = $('<div>');
        let html = '<p>{content}</p>';

        // 验证数据
        if (!link) {
          menu.dropPanel.focusFirstInput();
          return;
        }

        if (!reg.test(link)) {
          alert('视频链接格式错误！');
          menu.dropPanel.focusFirstInput();
          return;
        }

        if (isNaN(width) || isNaN(height)) {
          alert('宽度或高度不是数字！');
          return;
        }

        $link = $(link);

        // 设置高度和宽度
        $link.attr('width', width)
          .attr('height', height);

        // 拼接字符串
        html = html.replace('{content}', $div.append($link).html());

        // 执行命令
        editor.command(e, 'insertHtml', html);
        $linkInput.val('');
      });

      // 创建panel
      menu.dropPanel = new E.DropPanel(editor, menu, {
        $content: $content,
        width: 400
      });

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// location 菜单
  _e(function (E, $) {

    // 判断浏览器的 input 是否支持 keyup
    let inputKeyup = (function (input) {
      return 'onkeyup' in input;
    })(document.createElement('input'));

    // 百度地图的key
    E.baiduMapAk = 'TVhjYjq1ICT2qqL5LdS8mwas';

    // 一个页面中，如果有多个编辑器，地图会出现问题。这个参数记录一下，如果超过 1 就提示
    E.numberOfLocation = 0;

    E.createMenu(function (check) {
      let menuId = 'location';
      if (!check(menuId)) {
        return;
      }

      if (++E.numberOfLocation > 1) {
        E.error('目前不支持在一个页面多个编辑器上同时使用地图，可通过自定义菜单配置去掉地图菜单');
        return;
      }

      let editor = this;
      let config = editor.config;
      let lang = config.lang;
      let ak = config.mapAk;

      // 地图的变量存储到这个地方
      editor.mapData = {};
      let mapData = editor.mapData;

      // ---------- 地图事件 ----------
      mapData.markers = [];
      mapData.mapContainerId = 'map' + E.random();

      mapData.clearLocations = function () {
        let map = mapData.map;
        if (!map) {
          return;
        }
        map.clearOverlays();

        //同时，清空marker数组
        mapData.markers = [];
      };

      mapData.searchMap = function () {
        let map = mapData.map;
        if (!map) {
          return;
        }

        let BMap = window.BMap;
        let cityName = $cityInput.val();
        let locationName = $searchInput.val();
        let myGeo, marker;

        if (cityName !== '') {
          if (!locationName || locationName === '') {
            map.centerAndZoom(cityName, 11);
          }

          //地址解析
          if (locationName && locationName !== '') {
            myGeo = new BMap.Geocoder();
            // 将地址解析结果显示在地图上,并调整地图视野
            myGeo.getPoint(locationName, function (point) {
              if (point) {
                map.centerAndZoom(point, 13);
                marker = new BMap.Marker(point);
                map.addOverlay(marker);
                marker.enableDragging();  //允许拖拽
                mapData.markers.push(marker);  //将marker加入到数组中
              } else {
                // alert('未找到');
                map.centerAndZoom(cityName, 11);  //找不到则重新定位到城市
              }
            }, cityName);
          }
        } // if(cityName !== '')
      };

      // load script 之后的 callback
      let hasCallback = false;
      window.baiduMapCallBack = function () {
        // 避免重复加载
        if (hasCallback) {
          return;
        } else {
          hasCallback = true;
        }

        let BMap = window.BMap;
        if (!mapData.map) {
          // 创建Map实例
          mapData.map = new BMap.Map(mapData.mapContainerId);
        }
        let map = mapData.map;

        map.centerAndZoom(new BMap.Point(116.404, 39.915), 11);  // 初始化地图,设置中心点坐标和地图级别
        map.addControl(new BMap.MapTypeControl());   //添加地图类型控件
        map.setCurrentCity("北京");          // 设置地图显示的城市 此项是必须设置的
        map.enableScrollWheelZoom(true);     //开启鼠标滚轮缩放

        //根据IP定位
        function locationFun(result) {
          let cityName = result.name;
          map.setCenter(cityName);

          // 设置城市名称
          $cityInput.val(cityName);
          if (E.placeholder) {
            $searchInput.focus();
          }
          let timeoutId, searchFn;
          if (inputKeyup) {
            // 并绑定搜索事件 - input 支持 keyup
            searchFn = function (e) {
              if (e.type === 'keyup' && e.keyCode === 13) {
                e.preventDefault();
              }
              if (timeoutId) {
                clearTimeout(timeoutId);
              }
              timeoutId = setTimeout(mapData.searchMap, 500);
            };
            $cityInput.on('keyup change paste', searchFn);
            $searchInput.on('keyup change paste', searchFn);
          } else {
            // 并绑定搜索事件 - input 不支持 keyup
            searchFn = function () {
              if (!$content.is(':visible')) {
                // panel 不显示了，就不用再监控了
                clearTimeout(timeoutId);
                return;
              }

              let currentCity = '';
              let currentSearch = '';
              let city = $cityInput.val();
              let search = $searchInput.val();

              if (city !== currentCity || search !== currentSearch) {
                // 刚获取的数据和之前的数据不一致，执行查询
                mapData.searchMap();
                // 更新数据
                currentCity = city;
                currentSearch = search;
              }

              // 继续监控
              if (timeoutId) {
                clearTimeout(timeoutId);
              }
              timeoutId = setTimeout(searchFn, 1000);
            };
            // 开始监控
            timeoutId = setTimeout(searchFn, 1000);
          }
        }

        let myCity = new BMap.LocalCity();
        myCity.get(locationFun);

        //鼠标点击，创建位置
        map.addEventListener("click", function (e) {
          let marker = new BMap.Marker(new BMap.Point(e.point.lng, e.point.lat));
          map.addOverlay(marker);
          marker.enableDragging();
          mapData.markers.push(marker);  //加入到数组中
        }, false);
      };

      mapData.loadMapScript = function () {
        let script = document.createElement("script");
        script.type = "text/javascript";
        script.src = "https://api.map.baidu.com/api?v=2.0&ak=" + ak + "&s=1&callback=baiduMapCallBack";  // baiduMapCallBack是一个本地函数
        try {
          // IE10- 报错
          document.body.appendChild(script);
        } catch (ex) {
          E.error('加载地图过程中发生错误');
        }
      };

      // 初始化地图
      mapData.initMap = function () {
        if (window.BMap) {
          // 不是第一次，直接处理地图即可
          window.baiduMapCallBack();
        } else {
          // 第一次，先加载地图 script，再处理地图（script加载完自动执行处理）
          mapData.loadMapScript();
        }
      };

      // ---------- 创建 menu 对象 ----------

      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.location
      });

      editor.menus[menuId] = menu;

      // ---------- 构建UI ----------

      // panel content
      let $content = $('<div></div>');

      // 搜索框
      let $inputContainer = $('<div style="margin:10px 0;"></div>');
      let $cityInput = $('<input type="text"/>');
      $cityInput.css({
        width: '80px',
        'text-align': 'center'
      });
      let $searchInput = $('<input type="text"/>');
      $searchInput.css({
        width: '300px',
        'margin-left': '10px'
      }).attr('placeholder', lang.searchlocation);
      let $clearBtn = $('<button class="right link">' + lang.clearLocation + '</button>');
      $inputContainer.append($clearBtn)
        .append($cityInput)
        .append($searchInput);
      $content.append($inputContainer);

      // 清除位置按钮
      $clearBtn.click(function (e) {
        $searchInput.val('');
        $searchInput.focus();
        mapData.clearLocations();
        e.preventDefault();
      });

      // 地图
      let $map = $('<div id="' + mapData.mapContainerId + '"></div>');
      $map.css({
        height: '260px',
        width: '100%',
        position: 'relative',
        'margin-top': '10px',
        border: '1px solid #f1f1f1'
      });
      let $mapLoading = $('<span>' + lang.loading + '</span>');
      $mapLoading.css({
        position: 'absolute',
        width: '100px',
        'text-align': 'center',
        top: '45%',
        left: '50%',
        'margin-left': '-50px'
      });
      $map.append($mapLoading);
      $content.append($map);

      // 按钮
      let $btnContainer = $('<div style="margin:10px 0;"></div>');
      let $btnSubmit = $('<button class="right">' + lang.submit + '</button>');
      let $btnCancel = $('<button class="right gray">' + lang.cancel + '</button>');
      let $checkLabel = $('<label style="display:inline-block;margin-top:10px;color:#666;"></label>');
      let $check = $('<input type="checkbox">');
      $checkLabel.append($check).append('<span style="display:inline-block;margin-left:5px;">  ' + lang.dynamicMap + '</span>');
      $btnContainer.append($checkLabel)
        .append($btnSubmit)
        .append($btnCancel);
      $content.append($btnContainer);

      function callback() {
        $searchInput.val('');
      }

      // 『取消』按钮事件
      $btnCancel.click(function (e) {
        e.preventDefault();
        callback();
        menu.dropPanel.hide();
      });

      // 『确定』按钮事件
      $btnSubmit.click(function (e) {
        e.preventDefault();
        let map = mapData.map,
          isDynamic = $check.is(':checked'),
          markers = mapData.markers,

          center = map.getCenter(),
          centerLng = center.lng,
          centerLat = center.lat,

          zoom = map.getZoom(),

          size = map.getSize(),
          sizeWidth = size.width,
          sizeHeight = size.height,

          position,
          src,
          iframe;

        if (isDynamic) {
          //动态地址
          src = 'http://ueditor.baidu.com/ueditor/dialogs/map/show.html#';
        } else {
          //静态地址
          src = 'http://api.map.baidu.com/staticimage?';
        }

        //src参数
        src = src + 'center=' + centerLng + ',' + centerLat +
          '&zoom=' + zoom +
          '&width=' + sizeWidth +
          '&height=' + sizeHeight;
        if (markers.length > 0) {
          src = src + '&markers=';

          //添加所有的marker
          $.each(markers, function (key, value) {
            position = value.getPosition();
            if (key > 0) {
              src = src + '|';
            }
            src = src + position.lng + ',' + position.lat;
          });
        }

        if (isDynamic) {
          if (markers.length > 1) {
            alert(lang.langDynamicOneLocation);
            return;
          }

          src += '&markerStyles=l,A';

          //插入iframe
          iframe = '<iframe class="ueditor_baidumap" src="{src}" frameborder="0" width="' + sizeWidth + '" height="' + sizeHeight + '"></iframe>';
          iframe = iframe.replace('{src}', src);
          editor.command(e, 'insertHtml', iframe, callback);
        } else {
          //插入图片
          editor.command(e, 'insertHtml', '<img style="max-width:100%;" src="' + src + '"/>', callback);
        }
      });

      // 根据 UI 创建菜单 panel
      menu.dropPanel = new E.DropPanel(editor, menu, {
        $content: $content,
        width: 500
      });

      // ---------- 事件 ----------

      // render 时执行事件
      menu.onRender = function () {
        if (ak === E.baiduMapAk) {
          E.warn('建议在配置中自定义百度地图的mapAk，否则可能影响地图功能，文档：' + E.docsite);
        }
      };

      // click 事件
      menu.clickEvent = function (e) {
        let menu = this;
        let dropPanel = menu.dropPanel;
        let firstTime = false;

        // -------------隐藏-------------
        if (dropPanel.isShowing) {
          dropPanel.hide();
          return;
        }

        // -------------显示-------------
        if (!mapData.map) {
          // 第一次，先加载地图
          firstTime = true;
        }

        dropPanel.show();
        mapData.initMap();

        if (!firstTime) {
          $searchInput.focus();
        }
      };

    });

  });
// insertcode 菜单
  _e(function (E, $) {

    // 加载 highlightjs 代码
    function loadHljs() {
      if (E.userAgent.indexOf('MSIE 8') > 0) {
        // 不支持 IE8
        return;
      }
      if (window.hljs) {
        // 不要重复加载
        return;
      }
      let script = document.createElement("script");
      script.type = "text/javascript";
      script.src = "//cdn.bootcss.com/highlight.js/9.2.0/highlight.min.js";
      document.body.appendChild(script);
    }


    E.createMenu(function (check) {
      let menuId = 'insertcode';
      if (!check(menuId)) {
        return;
      }

      // 加载 highlightjs 代码
      setTimeout(loadHljs, 0);

      let editor = this;
      let config = editor.config;
      let lang = config.lang;
      let $txt = editor.txt.$txt;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.insertcode
      });

      // click 事件
      menu.clickEvent = function (e) {
        let menu = this;
        let dropPanel = menu.dropPanel;

        // 隐藏
        if (dropPanel.isShowing) {
          dropPanel.hide();
          return;
        }

        // 显示
        $textarea.val('');
        dropPanel.show();

        // highlightjs 语言列表
        let hljs = window.hljs;
        if (hljs && hljs.listLanguages) {
          if ($langSelect.children().length !== 0) {
            return;
          }
          $langSelect.css({
            'margin-top': '9px',
            'margin-left': '5px'
          });
          $.each(hljs.listLanguages(), function (key, lang) {
            if (lang === 'xml') {
              lang = 'html';
            }
            if (lang === config.codeDefaultLang) {
              $langSelect.append('<option value="' + lang + '" selected="selected">' + lang + '</option>');
            } else {
              $langSelect.append('<option value="' + lang + '">' + lang + '</option>');
            }
          });
        } else {
          $langSelect.hide();
        }
      };

      // 选中状态下的 click 事件
      menu.clickEventSelected = function (e) {
        let menu = this;
        let dropPanel = menu.dropPanel;

        // 隐藏
        if (dropPanel.isShowing) {
          dropPanel.hide();
          return;
        }

        // 显示
        dropPanel.show();

        let rangeElem = editor.getRangeElem();
        let targetElem = editor.getSelfOrParentByName(rangeElem, 'pre');
        let $targetElem;
        let className;
        if (targetElem) {
          // 确定找到 pre 之后，再找 code
          targetElem = editor.getSelfOrParentByName(rangeElem, 'code');
        }
        if (!targetElem) {
          return;
        }
        $targetElem = $(targetElem);

        // 赋值内容
        $textarea.val($targetElem.text());
        if ($langSelect) {
          // 赋值语言
          className = $targetElem.attr('class');
          if (className) {
            $langSelect.val(className.split(' ')[0]);
          }
        }
      };

      // 定义更新选中状态的事件
      menu.updateSelectedEvent = function () {
        let self = this; //菜单对象
        let editor = self.editor;
        let rangeElem;

        rangeElem = editor.getRangeElem();
        rangeElem = editor.getSelfOrParentByName(rangeElem, 'pre');

        if (rangeElem) {
          return true;
        }

        return false;
      };

      // 创建 panel
      let $content = $('<div></div>');
      let $textarea = $('<textarea></textarea>');
      let $langSelect = $('<select></select>');
      contentHandle($content);
      menu.dropPanel = new E.DropPanel(editor, menu, {
        $content: $content,
        width: 500
      });

      // 增加到editor对象中
      editor.menus[menuId] = menu;

      // ------ 增加 content 内容 ------
      function contentHandle($content) {
        // textarea 区域
        let $textareaContainer = $('<div></div>');
        $textareaContainer.css({
          margin: '15px 5px 5px 5px',
          height: '160px',
          'text-align': 'center'
        });
        $textarea.css({
          width: '100%',
          height: '100%',
          padding: '10px'
        });
        $textarea.on('keydown', function (e) {
          // 取消 tab 键默认行为
          if (e.keyCode === 9) {
            e.preventDefault();
          }
        });
        $textareaContainer.append($textarea);
        $content.append($textareaContainer);

        // 按钮区域
        let $btnContainer = $('<div></div>');
        let $btnSubmit = $('<button class="right">' + lang.submit + '</button>');
        let $btnCancel = $('<button class="right gray">' + lang.cancel + '</button>');

        $btnContainer.append($btnSubmit).append($btnCancel).append($langSelect);
        $content.append($btnContainer);

        // 取消按钮
        $btnCancel.click(function (e) {
          e.preventDefault();
          menu.dropPanel.hide();
        });

        // 确定按钮
        let codeTpl = '<pre style="max-width:100%;overflow-x:auto;"><code{#langClass}>{#content}</code></pre>';
        $btnSubmit.click(function (e) {
          e.preventDefault();
          let val = $textarea.val();
          if (!val) {
            // 无内容
            $textarea.focus();
            return;
          }

          let rangeElem = editor.getRangeElem();
          if ($.trim($(rangeElem).text()) && codeTpl.indexOf('<p><br></p>') !== 0) {
            codeTpl = '<p><br></p>' + codeTpl;
          }

          let lang = $langSelect ? $langSelect.val() : ''; // 获取高亮语言
          let langClass = '';
          let doHightlight = function () {
            $txt.find('pre code').each(function (i, block) {
              let $block = $(block);
              if ($block.attr('codemark')) {
                // 有 codemark 标记的代码块，就不再重新格式化了
                return;
              } else if (window.hljs) {
                // 新代码块，格式化之后，立即标记 codemark
                window.hljs.highlightBlock(block);
                $block.attr('codemark', '1');
              }
            });
          };

          // 语言高亮样式
          if (lang) {
            langClass = ' class="' + lang + ' hljs"';
          }

          // 替换标签
          val = val.replace(/&/gm, '&amp;')
            .replace(/</gm, '&lt;')
            .replace(/>/gm, '&gt;');

          // ---- menu 未选中状态 ----
          if (!menu.selected) {
            // 拼接html
            let html = codeTpl.replace('{#langClass}', langClass).replace('{#content}', val);
            editor.command(e, 'insertHtml', html, doHightlight);
            return;
          }

          // ---- menu 选中状态 ----
          let targetElem = editor.getSelfOrParentByName(rangeElem, 'pre');
          let $targetElem;
          if (targetElem) {
            // 确定找到 pre 之后，再找 code
            targetElem = editor.getSelfOrParentByName(rangeElem, 'code');
          }
          if (!targetElem) {
            return;
          }
          $targetElem = $(targetElem);

          function commandFn() {
            let className;
            if (lang) {
              className = $targetElem.attr('class');
              if (className !== lang + ' hljs') {
                $targetElem.attr('class', lang + ' hljs');
              }
            }
            $targetElem.html(val);
          }

          function callback() {
            editor.restoreSelectionByElem(targetElem);
            doHightlight();
          }

          editor.customCommand(e, commandFn, callback);
        });
      }

      // ------ enter 时，不另起标签，只换行 ------
      $txt.on('keydown', function (e) {
        if (e.keyCode !== 13) {
          return;
        }
        let rangeElem = editor.getRangeElem();
        let targetElem = editor.getSelfOrParentByName(rangeElem, 'code');
        if (!targetElem) {
          return;
        }

        editor.command(e, 'insertHtml', '\n');
      });

      // ------ 点击时，禁用其他标签 ------
      function updateMenu() {
        let rangeElem = editor.getRangeElem();
        let targetElem = editor.getSelfOrParentByName(rangeElem, 'code');
        if (targetElem) {
          // 在 code 之内，禁用其他菜单
          editor.disableMenusExcept('insertcode');
        } else {
          // 不是在 code 之内，启用其他菜单
          editor.enableMenusExcept('insertcode');
        }
      }

      $txt.on('keydown click', function (e) {
        // 此处必须使用 setTimeout 异步处理，否则不对
        setTimeout(updateMenu);
      });
    });

  });
// undo 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'undo';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.undo
      });

      // click 事件
      menu.clickEvent = function (e) {
        editor.undo();
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;


      // ------------ 初始化时、enter 时、打字中断时，做记录 ------------
      // ------------ ctrl + z 是调用记录撤销，而不是使用浏览器默认的撤销 ------------
      editor.ready(function () {
        let editor = this;
        let $txt = editor.txt.$txt;
        let timeoutId;

        // 执行undo记录
        function undo() {
          editor.undoRecord();
        }

        $txt.on('keydown', function (e) {
          let keyCode = e.keyCode;

          // 撤销 ctrl + z
          if (e.ctrlKey && keyCode === 90) {
            editor.undo();
            return;
          }

          if (keyCode === 13) {
            // enter 做记录
            undo();
          } else {
            // keyup 之后 1s 之内不操作，则做一次记录
            if (timeoutId) {
              clearTimeout(timeoutId);
            }
            timeoutId = setTimeout(undo, 1000);
          }
        });

        // 初始化做记录
        editor.undoRecord();
      });
    });

  });
// redo 菜单
  _e(function (E, $) {

    E.createMenu(function (check) {
      let menuId = 'redo';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let lang = editor.config.lang;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.redo
      });

      // click 事件
      menu.clickEvent = function (e) {
        editor.redo();
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// 全屏 菜单
  _e(function (E, $) {

    // 记录全屏时的scrollTop
    let scrollTopWhenFullScreen;

    E.createMenu(function (check) {
      let menuId = 'fullscreen';
      if (!check(menuId)) {
        return;
      }
      let editor = this;
      let $txt = editor.txt.$txt;
      let config = editor.config;
      let zIndexConfig = config.zindex || 10000;
      let lang = config.lang;

      let isSelected = false;
      let zIndex;

      let maxHeight;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,
        id: menuId,
        title: lang.fullscreen
      });

      // 定义click事件
      menu.clickEvent = function (e) {

        if (typeof editor.fullScreenClick === "function") {
          editor.fullScreenClick();
        }

        // 增加样式
        let $editorContainer = editor.$editorContainer;
        $editorContainer.addClass('wangEditor-fullscreen');

        // （先保存当前的）再设置z-index
        zIndex = $editorContainer.css('z-index');
        $editorContainer.css('z-index', zIndexConfig);

        let $wrapper;
        let txtHeight = $txt.height();
        let txtOuterHeight = $txt.outerHeight();

        if (editor.useMaxHeight) {
          // 记录 max-height，并暂时去掉maxheight
          maxHeight = $txt.css('max-height');
          $txt.css('max-height', 'none');

          // 如果使用了maxHeight， 将$txt从它的父元素中移出来
          $wrapper = $txt.parent();
          $wrapper.after($txt);
          $wrapper.remove();
          $txt.css('overflow-y', 'auto');
        }

        // 设置高度到全屏
        let menuContainer = editor.menuContainer;
        $txt.height(
          E.$window.height() -
          menuContainer.height() -
          (txtOuterHeight - txtHeight)  // 去掉内边距和外边距
        );

        // 取消menuContainer的内联样式（menu吸顶时，会为 menuContainer 设置一些内联样式）
        editor.menuContainer.$menuContainer.attr('style', '');

        // 保存状态
        isSelected = true;

        // 记录编辑器是否全屏
        editor.isFullScreen = true;

        // 记录设置全屏时的高度
        scrollTopWhenFullScreen = E.$window.scrollTop();
      };

      // 定义选中状态的 click 事件
      menu.clickEventSelected = function (e) {

        if (typeof editor.fullScreenClickSelected === "function") {
          editor.fullScreenClickSelected();
        }

        // 取消样式
        let $editorContainer = editor.$editorContainer;
        $editorContainer.removeClass('wangEditor-fullscreen');
        $editorContainer.css('z-index', zIndex);

        // 还原height
        if (editor.useMaxHeight) {
          $txt.css('max-height', maxHeight);
        } else {
          // editor.valueContainerHeight 在 editor.txt.initHeight() 中事先保存了
          editor.$valueContainer.css('height', editor.valueContainerHeight);
        }

        // 重新计算高度
        editor.txt.initHeight();

        // 保存状态
        isSelected = false;

        // 记录编辑器是否全屏
        editor.isFullScreen = false;

        // 还原scrollTop
        if (scrollTopWhenFullScreen != null) {
          E.$window.scrollTop(scrollTopWhenFullScreen);
        }
      };

      // 定义选中事件
      menu.updateSelectedEvent = function (e) {
        return isSelected;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// 渲染menus
  _e(function (E, $) {

    E.fn.renderMenus = function () {

      let editor = this;
      let menus = editor.menus;
      let menuIds = editor.config.menus;
      let menuContainer = editor.menuContainer;

      let menu;
      let groupIdx = 0;
      $.each(menuIds, function (k, v) {
        if (v === '|') {
          groupIdx++;
          return;
        }

        menu = menus[v];
        if (menu) {
          menu.render(groupIdx);
        }
      });
    };

  });
// 渲染menus
  _e(function (E, $) {

    E.fn.renderMenuContainer = function () {

      let editor = this;
      let menuContainer = editor.menuContainer;
      let $editorContainer = editor.$editorContainer;

      menuContainer.render();

    };

  });
// 渲染 txt
  _e(function (E, $) {

    E.fn.renderTxt = function () {

      let editor = this;
      let txt = editor.txt;

      txt.render();

      // ready 时候，计算txt的高度
      editor.ready(function () {
        txt.initHeight();
      });
    };

  });
// 渲染 container
  _e(function (E, $) {

    E.fn.renderEditorContainer = function () {

      let editor = this;
      let $valueContainer = editor.$valueContainer;
      let $editorContainer = editor.$editorContainer;
      let $txt = editor.txt.$txt;
      let $prev, $parent;

      // 将编辑器渲染到页面中
      if ($valueContainer === $txt) {
        $prev = editor.$prev;
        $parent = editor.$parent;

        if ($prev && $prev.length) {
          // 有前置节点，就插入到前置节点的后面
          $prev.after($editorContainer);
        } else {
          // 没有前置节点，就直接插入到父元素
          $parent.prepend($editorContainer);
        }

      } else {
        $valueContainer.after($editorContainer);
        $valueContainer.hide();
      }

      // 设置宽度（这样设置宽度有问题）
      // $editorContainer.css('width', $valueContainer.css('width'));
    };

  });
// 菜单事件
  _e(function (E, $) {

    // 绑定每个菜单的click事件
    E.fn.eventMenus = function () {

      let menus = this.menus;

      // 绑定菜单的点击事件
      $.each(menus, function (k, v) {
        v.bindEvent();
      });

    };

  });
// 菜单container事件
  _e(function (E, $) {

    E.fn.eventMenuContainer = function () {

    };

  });
// 编辑区域事件
  _e(function (E, $) {

    E.fn.eventTxt = function () {

      let txt = this.txt;

      // txt内容变化时，保存选区
      txt.saveSelectionEvent();

      // txt内容变化时，随时更新 value
      txt.updateValueEvent();

      // txt内容变化时，随时更新 menu style
      txt.updateMenuStyleEvent();

      // // 鼠标hover时，显示 p head 高度（暂时关闭这个功能）
      // if (!/ie/i.test(E.userAgent)) {
      //     // 暂时不支持IE
      //     txt.showHeightOnHover();
      // }
    };

  });
// 上传图片事件
  _e(function (E, $) {

    E.plugin(function () {
      let editor = this;
      let fns = editor.config.uploadImgFns; // editor.config.uploadImgFns = {} 在config文件中定义了

      // -------- 定义load函数 --------
      fns.onload || (fns.onload = function (resultText, xhr) {
        E.log('上传结束，返回结果为 ' + resultText);

        let editor = this;
        let originalName = editor.uploadImgOriginalName || '';  // 上传图片时，已经将图片的名字存在 editor.uploadImgOriginalName
        let img;
        if (resultText.indexOf('error|') === 0) {
          // 提示错误
          E.warn('上传失败：' + resultText.split('|')[1]);
          alert(resultText.split('|')[1]);
        } else {
          E.log('上传成功，即将插入编辑区域，结果为：' + resultText);

          // 将结果插入编辑器
          img = document.createElement('img');
          img.onload = function () {
            let html = '<img src="' + resultText + '" alt="' + originalName + '" style="max-width:100%;"/>';
            editor.command(null, 'insertHtml', html);

            E.log('已插入图片，地址 ' + resultText);
            img = null;
          };
          img.onerror = function () {
            E.error('使用返回的结果获取图片，发生错误。请确认以下结果是否正确：' + resultText);
            img = null;
          };
          img.src = resultText;
        }

      });

      // -------- 定义tiemout函数 --------
      fns.ontimeout || (fns.ontimeout = function (xhr) {
        E.error('上传图片超时');
        alert('上传图片超时');
      });

      // -------- 定义error函数 --------
      fns.onerror || (fns.onerror = function (xhr) {
        E.error('上传上图片发生错误');
        alert('上传上图片发生错误');
      });

    });
  });
// xhr 上传图片
  _e(function (E, $) {

    if (!window.FileReader || !window.FormData) {
      // 如果不支持html5的文档操作，直接返回
      return;
    }

    E.plugin(function () {

      let editor = this;
      let config = editor.config;
      let uploadImgUrl = config.uploadImgUrl;
      let uploadTimeout = config.uploadTimeout;

      // 获取配置中的上传事件
      let uploadImgFns = config.uploadImgFns;
      let onload = uploadImgFns.onload;
      let ontimeout = uploadImgFns.ontimeout;
      let onerror = uploadImgFns.onerror;

      if (!uploadImgUrl) {
        return;
      }

      // -------- 将以base64的图片url数据转换为Blob --------
      function convertBase64UrlToBlob(urlData, filetype) {
        //去掉url的头，并转换为byte
        let bytes = window.atob(urlData.split(',')[1]);

        //处理异常,将ascii码小于0的转换为大于0
        let ab = new ArrayBuffer(bytes.length);
        let ia = new Uint8Array(ab);
        let i;
        for (i = 0; i < bytes.length; i++) {
          ia[i] = bytes.charCodeAt(i);
        }

        return new Blob([ab], {type: filetype});
      }

      // -------- 插入图片的方法 --------
      function insertImg(src, event) {
        let img = document.createElement('img');
        img.onload = function () {
          let html = '<img src="' + src + '" style="max-width:100%;"/>';
          editor.command(event, 'insertHtml', html);

          E.log('已插入图片，地址 ' + src);
          img = null;
        };
        img.onerror = function () {
          E.error('使用返回的结果获取图片，发生错误。请确认以下结果是否正确：' + src);
          img = null;
        };
        img.src = src;
      }

      // -------- onprogress 事件 --------
      function updateProgress(e) {
        if (e.lengthComputable) {
          let percentComplete = e.loaded / e.total;
          editor.showUploadProgress(percentComplete * 100);
        }
      }

      // -------- xhr 上传图片 --------
      editor.xhrUploadImg = function (opt) {
        // opt 数据
        let event = opt.event;
        let fileName = opt.filename || '';
        let base64 = opt.base64;
        let fileType = opt.fileType || 'image/png'; // 无扩展名则默认使用 png
        let name = opt.name || 'wangEditor_upload_file';
        let loadfn = opt.loadfn || onload;
        let errorfn = opt.errorfn || onerror;
        let timeoutfn = opt.timeoutfn || ontimeout;

        // 上传参数（如 token）
        let params = editor.config.uploadParams || {};

        // headers
        let headers = editor.config.uploadHeaders || {};

        // 获取文件扩展名
        let fileExt = 'png';  // 默认为 png
        if (fileName.indexOf('.') > 0) {
          // 原来的文件名有扩展名
          fileExt = fileName.slice(fileName.lastIndexOf('.') - fileName.length + 1);
        } else if (fileType.indexOf('/') > 0 && fileType.split('/')[1]) {
          // 文件名没有扩展名，通过类型获取，如从 'image/png' 取 'png'
          fileExt = fileType.split('/')[1];
        }

        // ------------ begin 预览模拟上传 ------------
        if (E.isOnWebsite) {
          E.log('预览模拟上传');
          insertImg(base64, event);
          return;
        }
        // ------------ end 预览模拟上传 ------------

        // 变量声明
        let xhr = new XMLHttpRequest();
        let timeoutId;
        let src;
        let formData = new FormData();

        // 超时处理
        function timeoutCallback() {
          if (timeoutId) {
            clearTimeout(timeoutId);
          }
          if (xhr && xhr.abort) {
            xhr.abort();
          }

          // 超时了就阻止默认行为
          event.preventDefault();

          // 执行回调函数，提示什么内容，都应该在回调函数中定义
          timeoutfn && timeoutfn.call(editor, xhr);

          // 隐藏进度条
          editor.hideUploadProgress();
        }

        xhr.onload = function () {
          if (timeoutId) {
            clearTimeout(timeoutId);
          }

          // 记录文件名到 editor.uploadImgOriginalName ，插入图片时，可做 alt 属性用
          editor.uploadImgOriginalName = fileName;
          if (fileName.indexOf('.') > 0) {
            editor.uploadImgOriginalName = fileName.split('.')[0];
          }

          // 执行load函数，任何操作，都应该在load函数中定义
          loadfn && loadfn.call(editor, xhr.responseText, xhr);

          // 隐藏进度条
          editor.hideUploadProgress();
        };
        xhr.onerror = function () {
          if (timeoutId) {
            clearTimeout(timeoutId);
          }

          // 超时了就阻止默认行为
          event.preventDefault();

          // 执行error函数，错误提示，应该在error函数中定义
          errorfn && errorfn.call(editor, xhr);

          // 隐藏进度条
          editor.hideUploadProgress();
        };
        // xhr.onprogress = updateProgress;
        xhr.upload.onprogress = updateProgress;

        // 填充数据
        formData.append(name, convertBase64UrlToBlob(base64, fileType), E.random() + '.' + fileExt);

        // 添加参数
        $.each(params, function (key, value) {
          formData.append(key, value);
        });

        // 开始上传
        xhr.open('POST', uploadImgUrl, true);
        // xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");  // 将参数解析成传统form的方式上传

        // 修改自定义配置的headers
        $.each(headers, function (key, value) {
          xhr.setRequestHeader(key, value);
        });

        // 跨域上传时，传cookie
        xhr.withCredentials = true;

        // 发送数据
        xhr.send(formData);
        timeoutId = setTimeout(timeoutCallback, uploadTimeout);

        E.log('开始上传...并开始超时计算');
      };
    });
  });
// 进度条
  _e(function (E, $) {

    E.plugin(function () {

      let editor = this;
      let menuContainer = editor.menuContainer;
      let menuHeight = menuContainer.height();
      let $editorContainer = editor.$editorContainer;
      let width = $editorContainer.width();
      let $progress = $('<div class="wangEditor-upload-progress"></div>');

      // 渲染事件
      let isRender = false;

      function render() {
        if (isRender) {
          return;
        }
        isRender = true;

        $progress.css({
          top: menuHeight + 'px'
        });
        $editorContainer.append($progress);
      }

      // ------ 显示进度 ------
      editor.showUploadProgress = function (progress) {
        if (timeoutId) {
          clearTimeout(timeoutId);
        }

        // 显示之前，先判断是否渲染
        render();

        $progress.show();
        $progress.width(progress * width / 100);
      };

      // ------ 隐藏进度条 ------
      let timeoutId;

      function hideProgress() {
        $progress.hide();
        timeoutId = null;
      }

      editor.hideUploadProgress = function (time) {
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
        time = time || 750;
        timeoutId = setTimeout(hideProgress, time);
      };
    });
  });
// upload img 插件
  _e(function (E, $) {

    E.plugin(function () {
      let editor = this;
      let config = editor.config;
      let uploadImgUrl = config.uploadImgUrl;
      let uploadTimeout = config.uploadTimeout;
      let event;

      if (!uploadImgUrl) {
        return;
      }

      // 获取editor的上传dom
      let $uploadContent = editor.$uploadContent;
      if (!$uploadContent) {
        return;
      }

      // 自定义UI，并添加到上传dom节点上
      let $uploadIcon = $('<div class="upload-icon-container"><i class="wangeditor-menu-img-upload"></i></div>');
      $uploadContent.append($uploadIcon);

      // ---------- 构建上传对象 ----------
      let upfile = new E.UploadFile({
        editor: editor,
        uploadUrl: uploadImgUrl,
        timeout: uploadTimeout,
        fileAccept: 'image/jpg,image/jpeg,image/png,image/gif,image/bmp'    // 只允许选择图片
      });

      // 选择本地文件，上传
      $uploadIcon.click(function (e) {
        event = e;
        upfile.selectFiles();
      });
    });
  });
// h5 方式上传图片
  _e(function (E, $) {

    if (!window.FileReader || !window.FormData) {
      // 如果不支持html5的文档操作，直接返回
      return;
    }

    // 构造函数
    let UploadFile = function (opt) {
      this.editor = opt.editor;
      this.uploadUrl = opt.uploadUrl;
      this.timeout = opt.timeout;
      this.fileAccept = opt.fileAccept;
      this.multiple = true;
    };

    UploadFile.fn = UploadFile.prototype;

    // clear
    UploadFile.fn.clear = function () {
      this.$input.val('');
      E.log('input value 已清空');
    };

    // 渲染
    UploadFile.fn.render = function () {
      let self = this;
      if (self._hasRender) {
        // 不要重复渲染
        return;
      }

      E.log('渲染dom');

      let fileAccept = self.fileAccept;
      let acceptTpl = fileAccept ? 'accept="' + fileAccept + '"' : '';
      let multiple = self.multiple;
      let multipleTpl = multiple ? 'multiple="multiple"' : '';
      let $input = $('<input type="file" ' + acceptTpl + ' ' + multipleTpl + '/>');
      let $container = $('<div style="visibility:hidden;"></div>');

      $container.append($input);
      E.$body.append($container);

      // onchange 事件
      $input.on('change', function (e) {
        self.selected(e, $input.get(0));
      });

      // 记录对象数据
      self.$input = $input;

      // 记录
      self._hasRender = true;
    };

    // 选择
    UploadFile.fn.selectFiles = function () {
      let self = this;

      E.log('使用 html5 方式上传');

      // 先渲染
      self.render();

      // 选择
      E.log('选择文件');
      self.$input.click();
    };

    // 选中文件之后
    UploadFile.fn.selected = function (e, input) {
      let self = this;
      let files = input.files || [];
      if (files.length === 0) {
        return;
      }

      E.log('选中 ' + files.length + ' 个文件');

      // 遍历选中的文件，预览、上传
      $.each(files, function (key, value) {
        self.upload(value);
      });
    };

    // 上传单个文件
    UploadFile.fn.upload = function (file) {
      let self = this;
      let editor = self.editor;
      let filename = file.name || '';
      let fileType = file.type || '';
      let uploadImgFns = editor.config.uploadImgFns;
      let uploadFileName = editor.config.uploadImgFileName || 'wangEditorH5File';
      let onload = uploadImgFns.onload;
      let ontimeout = uploadImgFns.ontimeout;
      let onerror = uploadImgFns.onerror;
      let reader = new FileReader();

      if (!onload || !ontimeout || !onerror) {
        E.error('请为编辑器配置上传图片的 onload ontimeout onerror 回调事件');
        return;
      }


      E.log('开始执行 ' + filename + ' 文件的上传');

      // 清空 input 数据
      function clearInput() {
        self.clear();
      }

      // onload事件
      reader.onload = function (e) {
        E.log('已读取' + filename + '文件');

        let base64 = e.target.result || this.result;
        editor.xhrUploadImg({
          event: e,
          filename: filename,
          base64: base64,
          fileType: fileType,
          name: uploadFileName,
          loadfn: function (resultText, xhr) {
            clearInput();
            // 执行配置中的方法
            let editor = this;
            onload.call(editor, resultText, xhr);
          },
          errorfn: function (xhr) {
            clearInput();
            if (E.isOnWebsite) {
              alert('wangEditor官网暂时没有服务端，因此报错。实际项目中不会发生');
            }
            // 执行配置中的方法
            let editor = this;
            onerror.call(editor, xhr);
          },
          timeoutfn: function (xhr) {
            clearInput();
            if (E.isOnWebsite) {
              alert('wangEditor官网暂时没有服务端，因此超时。实际项目中不会发生');
            }
            // 执行配置中的方法
            let editor = this;
            ontimeout(editor, xhr);
          }
        });
      };

      // 开始取文件
      reader.readAsDataURL(file);
    };

    // 暴露给 E
    E.UploadFile = UploadFile;

  });
// form方式上传图片
  _e(function (E, $) {

    if (window.FileReader && window.FormData) {
      // 如果支持 html5 上传，则返回
      return;
    }

    // 构造函数
    let UploadFile = function (opt) {
      this.editor = opt.editor;
      this.uploadUrl = opt.uploadUrl;
      this.timeout = opt.timeout;
      this.fileAccept = opt.fileAccept;
      this.multiple = false;
    };

    UploadFile.fn = UploadFile.prototype;

    // clear
    UploadFile.fn.clear = function () {
      this.$input.val('');
      E.log('input value 已清空');
    };

    // 隐藏modal
    UploadFile.fn.hideModal = function () {
      this.modal.hide();
    };

    // 渲染
    UploadFile.fn.render = function () {
      let self = this;
      let editor = self.editor;
      let uploadFileName = editor.config.uploadImgFileName || 'wangEditorFormFile';
      if (self._hasRender) {
        // 不要重复渲染
        return;
      }

      // 服务器端路径
      let uploadUrl = self.uploadUrl;

      E.log('渲染dom');

      // 创建 form 和 iframe
      let iframeId = 'iframe' + E.random();
      let $iframe = $('<iframe name="' + iframeId + '" id="' + iframeId + '" frameborder="0" width="0" height="0"></iframe>');
      let multiple = self.multiple;
      let multipleTpl = multiple ? 'multiple="multiple"' : '';
      let $p = $('<p>选择图片并上传</p>');
      let $input = $('<input type="file" ' + multipleTpl + ' name="' + uploadFileName + '"/>');
      let $btn = $('<input type="submit" value="上传"/>');
      let $form = $('<form enctype="multipart/form-data" method="post" action="' + uploadUrl + '" target="' + iframeId + '"></form>');
      let $container = $('<div style="margin:10px 20px;"></div>');

      $form.append($p).append($input).append($btn);

      // 增加用户配置的参数，如 token
      $.each(editor.config.uploadParams, function (key, value) {
        $form.append($('<input type="hidden" name="' + key + '" value="' + value + '"/>'));
      });

      $container.append($form);
      $container.append($iframe);

      self.$input = $input;
      self.$iframe = $iframe;

      // 生成 modal
      let modal = new E.Modal(editor, undefined, {
        $content: $container
      });
      self.modal = modal;

      // 记录
      self._hasRender = true;
    };

    // 绑定 iframe load 事件
    UploadFile.fn.bindLoadEvent = function () {
      let self = this;
      if (self._hasBindLoad) {
        // 不要重复绑定
        return;
      }

      let editor = self.editor;
      let $iframe = self.$iframe;
      let iframe = $iframe.get(0);
      let iframeWindow = iframe.contentWindow;
      let onload = editor.config.uploadImgFns.onload;

      // 定义load事件
      function onloadFn() {
        let resultText = $.trim(iframeWindow.document.body.innerHTML);
        if (!resultText) {
          return;
        }

        // 获取文件名
        let fileFullName = self.$input.val();  // 结果如 C:\folder\abc.png 格式
        let fileOriginalName = fileFullName;
        if (fileFullName.lastIndexOf('\\') >= 0) {
          // 获取 abc.png 格式
          fileOriginalName = fileFullName.slice(fileFullName.lastIndexOf('\\') + 1);
          if (fileOriginalName.indexOf('.') > 0) {
            // 获取 abc （即不带扩展名的文件名）
            fileOriginalName = fileOriginalName.split('.')[0];
          }
        }

        // 将文件名暂存到 editor.uploadImgOriginalName ，插入图片时，可作为 alt 属性来用
        editor.uploadImgOriginalName = fileOriginalName;

        // 执行load函数，插入图片的操作，应该在load函数中执行
        onload.call(editor, resultText);

        // 清空 input 数据
        self.clear();

        // 隐藏modal
        self.hideModal();
      }

      // 绑定 load 事件
      if (iframe.attachEvent) {
        iframe.attachEvent('onload', onloadFn);
      } else {
        iframe.onload = onloadFn;
      }

      // 记录
      self._hasBindLoad = true;
    };

    UploadFile.fn.show = function () {
      let self = this;
      let modal = self.modal;

      function show() {
        modal.show();
        self.bindLoadEvent();
      }

      setTimeout(show);
    };

    // 选择
    UploadFile.fn.selectFiles = function () {
      let self = this;

      E.log('使用 form 方式上传');

      // 先渲染
      self.render();

      // 先清空
      self.clear();

      // 显示
      self.show();
    };

    // 暴露给 E
    E.UploadFile = UploadFile;

  });
// upload img 插件 粘贴图片
  _e(function (E, $) {

    E.plugin(function () {
      let editor = this;
      let txt = editor.txt;
      let $txt = txt.$txt;
      let config = editor.config;
      let uploadImgUrl = config.uploadImgUrl;
      let uploadFileName = config.uploadImgFileName || 'wangEditorPasteFile';
      let pasteEvent;
      let $imgsBeforePaste;

      // 未配置上传图片url，则忽略
      if (!uploadImgUrl) {
        return;
      }

      // -------- 非 chrome 下，通过查找粘贴的图片的方式上传 --------
      function findPasteImgAndUpload() {
        let reg = /^data:(image\/\w+);base64/;
        let $imgs = $txt.find('img');

        E.log('粘贴后，检查到编辑器有' + $imgs.length + '个图片。开始遍历图片，试图找到刚刚粘贴过来的图片');

        $.each($imgs, function () {
          let img = this;
          let $img = $(img);
          let flag;
          let base64 = $img.attr('src');
          let type;

          // 判断当前图片是否是粘贴之前的
          $imgsBeforePaste.each(function () {
            if (img === this) {
              // 当前图片是粘贴之前的
              flag = true;
              return false;
            }
          });

          // 当前图片是粘贴之前的，则忽略
          if (flag) {
            return;
          }

          E.log('找到一个粘贴过来的图片');

          if (reg.test(base64)) {
            // 得到的粘贴的图片是 base64 格式，符合要求
            E.log('src 是 base64 格式，可以上传');
            type = base64.match(reg)[1];
            editor.xhrUploadImg({
              event: pasteEvent,
              base64: base64,
              fileType: type,
              name: uploadFileName
            });
          } else {
            E.log('src 为 ' + base64 + ' ，不是 base64 格式，暂时不支持上传');
          }

          // 最终移除原图片
          $img.remove();
        });

        E.log('遍历结束');
      }

      // 开始监控粘贴事件
      $txt.on('paste', function (e) {
        pasteEvent = e;
        let data = pasteEvent.clipboardData || pasteEvent.originalEvent.clipboardData;
        let text;
        let items;

        // -------- 试图获取剪切板中的文字，有文字的情况下，就不处理图片粘贴 --------
        if (data == null) {
          text = window.clipboardData && window.clipboardData.getData('text');
        } else {
          text = data.getData('text/plain') || data.getData('text/html');
        }
        if (text) {
          return;
        }

        items = data && data.items;
        if (items) {
          // -------- chrome 可以用 data.items 取出图片 -----
          E.log('通过 data.items 得到了数据');

          $.each(items, function (key, value) {
            let fileType = value.type || '';
            if (fileType.indexOf('image') < 0) {
              // 不是图片
              return;
            }

            let file = value.getAsFile();
            let reader = new FileReader();

            E.log('得到一个粘贴图片');

            reader.onload = function (e) {
              E.log('读取到粘贴的图片');

              // 执行上传
              let base64 = e.target.result || this.result;
              editor.xhrUploadImg({
                event: pasteEvent,
                base64: base64,
                fileType: fileType,
                name: uploadFileName
              });
            };

            //读取粘贴的文件
            reader.readAsDataURL(file);
          });
        } else {
          // -------- 非 chrome 不能用 data.items 取图片 -----

          E.log('未从 data.items 得到数据，使用检测粘贴图片的方式');

          // 获取
          $imgsBeforePaste = $txt.find('img');
          E.log('粘贴前，检查到编辑器有' + $imgsBeforePaste.length + '个图片');

          // 异步上传找到的图片
          setTimeout(findPasteImgAndUpload, 0);
        }
      });

    });
  });
// 拖拽上传图片 插件
  _e(function (E, $) {

    E.plugin(function () {

      let editor = this;
      let txt = editor.txt;
      let $txt = txt.$txt;
      let config = editor.config;
      let uploadImgUrl = config.uploadImgUrl;
      let uploadFileName = config.uploadImgFileName || 'wangEditorDragFile';

      // 未配置上传图片url，则忽略
      if (!uploadImgUrl) {
        return;
      }

      // 阻止浏览器默认行为
      E.$document.on('dragleave drop dragenter dragover', function (e) {
        e.preventDefault();
      });

      // 监控 $txt drop 事件
      $txt.on('drop', function (dragEvent) {
        dragEvent.preventDefault();

        let originalEvent = dragEvent.originalEvent;
        let files = originalEvent.dataTransfer && originalEvent.dataTransfer.files;

        if (!files || !files.length) {
          return;
        }

        $.each(files, function (k, file) {
          let type = file.type;
          let name = file.name;

          if (type.indexOf('image/') < 0) {
            // 只接收图片
            return;
          }

          E.log('得到图片 ' + name);

          let reader = new FileReader();
          reader.onload = function (e) {
            E.log('读取到图片 ' + name);

            // 执行上传
            let base64 = e.target.result || this.result;
            editor.xhrUploadImg({
              event: dragEvent,
              base64: base64,
              fileType: type,
              name: uploadFileName
            });
          };

          //读取粘贴的文件
          reader.readAsDataURL(file);

        });
      });
    });

  });
// 编辑器区域 table toolbar
  _e(function (E, $) {

    E.plugin(function () {
      let editor = this;
      let txt = editor.txt;
      let $txt = txt.$txt;
      let html = '';
      // 说明：设置了 max-height 之后，$txt.parent() 负责滚动处理
      let $currentTxt = editor.useMaxHeight ? $txt.parent() : $txt;
      let $currentTable;

      // 用到的dom节点
      let isRendered = false;
      let $toolbar = $('<div class="txt-toolbar"></div>');
      let $triangle = $('<div class="tip-triangle"></div>');
      let $delete = $('<a href="#"><i class="wangeditor-menu-img-trash-o"></i></a>');
      let $zoomSmall = $('<a href="#"><i class="wangeditor-menu-img-search-minus"></i></a>');
      let $zoomBig = $('<a href="#"><i class="wangeditor-menu-img-search-plus"></i></a>');

      // 渲染到页面
      function render() {
        if (isRendered) {
          return;
        }

        // 绑定事件
        bindEvent();

        // 拼接 渲染到页面上
        $toolbar.append($triangle)
          .append($delete)
          .append($zoomSmall)
          .append($zoomBig);
        editor.$editorContainer.append($toolbar);
        isRendered = true;
      }

      // 绑定事件
      function bindEvent() {
        // 统一执行命令的方法
        let commandFn;

        function command(e, callback) {
          // 执行命令之前，先存储html内容
          html = $txt.html();
          // 监控内容变化
          let cb = function () {
            if (callback) {
              callback();
            }
            if (html !== $txt.html()) {
              $txt.change();
            }
          };
          // 执行命令
          if (commandFn) {
            editor.customCommand(e, commandFn, cb);
          }
        }

        // 删除
        $delete.click(function (e) {
          commandFn = function () {
            $currentTable.remove();
          };
          command(e, function () {
            setTimeout(hide, 100);
          });
        });

        // 放大
        $zoomBig.click(function (e) {
          commandFn = function () {
            $currentTable.css({
              width: '100%'
            });
          };
          command(e, function () {
            setTimeout(show);
          });
        });

        // 缩小
        $zoomSmall.click(function (e) {
          commandFn = function () {
            $currentTable.css({
              width: 'auto'
            });
          };
          command(e, function () {
            setTimeout(show);
          });
        });
      }

      // 显示 toolbar
      function show() {
        if (editor._disabled) {
          // 编辑器已经被禁用，则不让显示
          return;
        }
        if ($currentTable == null) {
          return;
        }
        $currentTable.addClass('clicked');
        let tablePosition = $currentTable.position();
        let tableTop = tablePosition.top;
        let tableLeft = tablePosition.left;
        let tableHeight = $currentTable.outerHeight();
        let tableWidth = $currentTable.outerWidth();

        // --- 定位 toolbar ---

        // 计算初步结果
        let top = tableTop + tableHeight;
        let left = tableLeft;
        let marginLeft = 0;

        let txtTop = $currentTxt.position().top;
        let txtHeight = $currentTxt.outerHeight();
        if (top > (txtTop + txtHeight)) {
          // top 不得超出编辑范围
          top = txtTop + txtHeight;
        }

        // 显示（方便计算 margin）
        $toolbar.show();

        // 计算 margin
        let width = $toolbar.outerWidth();
        marginLeft = tableWidth / 2 - width / 2;

        // 定位
        $toolbar.css({
          top: top + 5,
          left: left,
          'margin-left': marginLeft
        });
        // 如果定位太靠左了
        if (marginLeft < 0) {
          // 得到三角形的margin-left
          $toolbar.css('margin-left', '0');
          $triangle.hide();
        } else {
          $triangle.show();
        }
      }

      // 隐藏 toolbar
      function hide() {
        if ($currentTable == null) {
          return;
        }
        $currentTable.removeClass('clicked');
        $currentTable = null;
        $toolbar.hide();
      }

      // click table 事件
      $currentTxt.on('click', 'table', function (e) {
        let $table = $(e.currentTarget);

        // 渲染
        render();

        if ($currentTable && ($currentTable.get(0) === $table.get(0))) {
          setTimeout(hide, 100);
          return;
        }

        // 显示 toolbar
        $currentTable = $table;
        show();

        // 阻止冒泡
        e.preventDefault();
        e.stopPropagation();

      }).on('click keydown scroll', function (e) {
        setTimeout(hide, 100);
      });
      E.$body.on('click keydown scroll', function (e) {
        setTimeout(hide, 100);
      });
    });

  });
// 编辑器区域 img toolbar
  _e(function (E, $) {

    if (E.userAgent.indexOf('MSIE 8') > 0) {
      return;
    }

    E.plugin(function () {
      let editor = this;
      let lang = editor.config.lang;
      let txt = editor.txt;
      let $txt = txt.$txt;
      let html = '';
      // 说明：设置了 max-height 之后，$txt.parent() 负责滚动处理
      let $currentTxt = editor.useMaxHeight ? $txt.parent() : $txt;
      let $editorContainer = editor.$editorContainer;
      let $currentImg;
      let currentLink = '';

      // 用到的dom节点
      let isRendered = false;
      let $dragPoint = $('<div class="img-drag-point"></div>');

      let $toolbar = $('<div class="txt-toolbar"></div>');
      let $triangle = $('<div class="tip-triangle"></div>');

      let $menuContainer = $('<div></div>');
      let $delete = $('<a href="#"><i class="wangeditor-menu-img-trash-o"></i></a>');
      let $zoomSmall = $('<a href="#"><i class="wangeditor-menu-img-search-minus"></i></a>');
      let $zoomBig = $('<a href="#"><i class="wangeditor-menu-img-search-plus"></i></a>');
      // let $floatLeft = $('<a href="#"><i class="wangeditor-menu-img-align-left"></i></a>');
      // let $noFloat = $('<a href="#"><i class="wangeditor-menu-img-align-justify"></i></a>');
      // let $floatRight = $('<a href="#"><i class="wangeditor-menu-img-align-right"></i></a>');
      let $alignLeft = $('<a href="#"><i class="wangeditor-menu-img-align-left"></i></a>');
      let $alignCenter = $('<a href="#"><i class="wangeditor-menu-img-align-center"></i></a>');
      let $alignRight = $('<a href="#"><i class="wangeditor-menu-img-align-right"></i></a>');
      let $link = $('<a href="#"><i class="wangeditor-menu-img-link"></i></a>');
      let $unLink = $('<a href="#"><i class="wangeditor-menu-img-unlink"></i></a>');

      let $linkInputContainer = $('<div style="display:none;"></div>');
      let $linkInput = $('<input type="text" style="height:26px; margin-left:10px; width:200px;"/>');
      let $linkBtnSubmit = $('<button class="right">' + lang.submit + '</button>');
      let $linkBtnCancel = $('<button class="right gray">' + lang.cancel + '</button>');

      // 记录是否正在拖拽
      let isOnDrag = false;

      // 获取 / 设置 链接
      function imgLink(e, url) {
        if (!$currentImg) {
          return;
        }
        let commandFn;
        let callback = function () {
          // 及时保存currentLink
          if (url != null) {
            currentLink = url;
          }
          if (html !== $txt.html()) {
            $txt.change();
          }
        };
        let $link;
        let inLink = false;
        let $parent = $currentImg.parent();
        if ($parent.get(0).nodeName.toLowerCase() === 'a') {
          // 父元素就是图片链接
          $link = $parent;
          inLink = true;
        } else {
          // 父元素不是图片链接，则重新创建一个链接
          $link = $('<a target="_blank"></a>');
        }

        if (url == null) {
          // url 无值，是获取链接
          return $link.attr('href') || '';
        } else if (url === '') {
          // url 是空字符串，是取消链接
          if (inLink) {
            commandFn = function () {
              $currentImg.unwrap();
            };
          }
        } else {
          // url 有值，是设置链接
          if (url === currentLink) {
            return;
          }
          commandFn = function () {
            $link.attr('href', url);

            if (!inLink) {
              // 当前图片未包含在链接中，则包含进来
              $currentImg.wrap($link);
            }
          };
        }

        // 执行命令
        if (commandFn) {
          // 记录下执行命令之前的html内容
          html = $txt.html();
          // 执行命令
          editor.customCommand(e, commandFn, callback);
        }
      }

      // 渲染到页面
      function render() {
        if (isRendered) {
          return;
        }

        // 绑定事件
        bindToolbarEvent();
        bindDragEvent();

        // 菜单放入 container
        $menuContainer.append($delete)
          .append($zoomSmall)
          .append($zoomBig)
          // .append($floatLeft)
          // .append($noFloat)
          // .append($floatRight);
          .append($alignLeft)
          .append($alignCenter)
          .append($alignRight)
          .append($link)
          .append($unLink);

        // 链接input放入container
        $linkInputContainer.append($linkInput)
          .append($linkBtnCancel)
          .append($linkBtnSubmit);

        // 拼接 渲染到页面上
        $toolbar.append($triangle)
          .append($menuContainer)
          .append($linkInputContainer);

        editor.$editorContainer.append($toolbar).append($dragPoint);
        isRendered = true;
      }

      // 绑定toolbar事件
      function bindToolbarEvent() {
        // 统一执行命令的方法
        let commandFn;

        function customCommand(e, callback) {
          let cb;
          // 记录下执行命令之前的html内容
          html = $txt.html();
          cb = function () {
            if (callback) {
              callback();
            }
            if (html !== $txt.html()) {
              $txt.change();
            }
          };
          // 执行命令
          if (commandFn) {
            editor.customCommand(e, commandFn, cb);
          }
        }

        // 删除
        $delete.click(function (e) {
          // 删除之前先unlink
          imgLink(e, '');

          // 删除图片
          commandFn = function () {
            $currentImg.remove();
          };
          customCommand(e, function () {
            setTimeout(hide, 100);
          });
        });

        // 放大
        $zoomBig.click(function (e) {
          commandFn = function () {
            let img = $currentImg.get(0);
            let width = img.width;
            let height = img.height;
            width = width * 1.1;
            height = height * 1.1;

            $currentImg.css({
              width: width + 'px',
              height: height + 'px'
            });
          };
          customCommand(e, function () {
            setTimeout(show);
          });
        });

        // 缩小
        $zoomSmall.click(function (e) {
          commandFn = function () {
            let img = $currentImg.get(0);
            let width = img.width;
            let height = img.height;
            width = width * 0.9;
            height = height * 0.9;

            $currentImg.css({
              width: width + 'px',
              height: height + 'px'
            });
          };
          customCommand(e, function () {
            setTimeout(show);
          });
        });

        // // 左浮动
        // $floatLeft.click(function (e) {
        //     commandFn = function () {
        //         $currentImg.css({
        //             float: 'left'
        //         });
        //     };
        //     customCommand(e, function () {
        //         setTimeout(hide, 100);
        //     });
        // });

        // alignLeft
        $alignLeft.click(function (e) {
          commandFn = function () {
            // 如果 img 增加了链接，那么 img.parent() 就是 a 标签，设置 align 没用的，因此必须找到 P 父节点来设置 align
            $currentImg.parents('p').css({
              'text-align': 'left'
            }).attr('align', 'left');
            console.log("Here.");
          };
          customCommand(e, function () {
            setTimeout(hide, 100);
          });
        });

        // // 右浮动
        // $floatRight.click(function (e) {
        //     commandFn = function () {
        //         $currentImg.css({
        //             float: 'right'
        //         });
        //     };
        //     customCommand(e, function () {
        //         setTimeout(hide, 100);
        //     });
        // });

        // alignRight
        $alignRight.click(function (e) {
          commandFn = function () {
            // 如果 img 增加了链接，那么 img.parent() 就是 a 标签，设置 align 没用的，因此必须找到 P 父节点来设置 align
            $currentImg.parents('p').css({
              'text-align': 'right'
            }).attr('align', 'right');
          };
          customCommand(e, function () {
            setTimeout(hide, 100);
          });
        });

        // // 无浮动
        // $noFloat.click(function (e) {
        //     commandFn = function () {
        //         $currentImg.css({
        //             float: 'none'
        //         });
        //     };
        //     customCommand(e, function () {
        //         setTimeout(hide, 100);
        //     });
        // });

        // alignCenter
        $alignCenter.click(function (e) {
          commandFn = function () {
            // 如果 img 增加了链接，那么 img.parent() 就是 a 标签，设置 align 没用的，因此必须找到 P 父节点来设置 align
            $currentImg.parents('p').css({
              'text-align': 'center'
            }).attr('align', 'center');
          };
          customCommand(e, function () {
            setTimeout(hide, 100);
          });
        });

        // link
        // 显示链接input
        $link.click(function (e) {
          e.preventDefault();

          // 获取当前链接，并显示
          currentLink = imgLink(e);
          $linkInput.val(currentLink);

          $menuContainer.hide();
          $linkInputContainer.show();
        });
        // 设置链接
        $linkBtnSubmit.click(function (e) {
          e.preventDefault();

          let url = $.trim($linkInput.val());
          if (url) {
            // 设置链接，同时会自动更新 currentLink 的值
            imgLink(e, url);
          }

          // 隐藏 toolbar
          setTimeout(hide);
        });
        // 取消设置链接
        $linkBtnCancel.click(function (e) {
          e.preventDefault();

          // 重置链接 input
          $linkInput.val(currentLink);

          $menuContainer.show();
          $linkInputContainer.hide();
        });

        // unlink
        $unLink.click(function (e) {
          e.preventDefault();

          // 执行 unlink
          imgLink(e, '');

          // 隐藏 toolbar
          setTimeout(hide);
        });
      }

      // 绑定drag事件
      function bindDragEvent() {
        let _x, _y;
        let dragMarginLeft, dragMarginTop;
        let imgWidth, imgHeight;

        function mousemove(e) {
          let diffX, diffY;

          // 计算差额
          diffX = e.pageX - _x;
          diffY = e.pageY - _y;

          // --------- 计算拖拽点的位置 ---------
          let currentDragMarginLeft = dragMarginLeft + diffX;
          let currentDragMarginTop = dragMarginTop + diffY;
          $dragPoint.css({
            'margin-left': currentDragMarginLeft,
            'margin-top': currentDragMarginTop
          });

          // --------- 计算图片的大小 ---------
          let currentImgWidth = imgWidth + diffX;
          let currentImggHeight = imgHeight + diffY;
          $currentImg && $currentImg.css({
            width: currentImgWidth,
            height: currentImggHeight
          });
        }

        $dragPoint.on('mousedown', function (e) {
          if (!$currentImg) {
            return;
          }
          // 当前鼠标位置
          _x = e.pageX;
          _y = e.pageY;

          // 当前拖拽点的位置
          dragMarginLeft = parseFloat($dragPoint.css('margin-left'), 10);
          dragMarginTop = parseFloat($dragPoint.css('margin-top'), 10);

          // 当前图片的大小
          imgWidth = $currentImg.width();
          imgHeight = $currentImg.height();

          // 隐藏 $toolbar
          $toolbar.hide();

          // 绑定计算事件
          E.$document.on('mousemove._dragResizeImg', mousemove);
          E.$document.on('mouseup._dragResizeImg', function (e) {
            // 取消绑定
            E.$document.off('mousemove._dragResizeImg');
            E.$document.off('mouseup._dragResizeImg');

            // 隐藏，并还原拖拽点的位置
            hide();
            $dragPoint.css({
              'margin-left': dragMarginLeft,
              'margin-top': dragMarginTop
            });

            // 记录
            isOnDrag = false;
          });

          // 记录
          isOnDrag = true;
        });
      }

      // 显示 toolbar
      function show() {
        if (editor._disabled) {
          // 编辑器已经被禁用，则不让显示
          return;
        }
        if ($currentImg == null) {
          return;
        }
        $currentImg.addClass('clicked');
        let imgPosition = $currentImg.position();
        let imgTop = imgPosition.top;
        let imgLeft = imgPosition.left;
        let imgHeight = $currentImg.outerHeight();
        let imgWidth = $currentImg.outerWidth();


        // --- 定位 dragpoint ---
        $dragPoint.css({
          top: imgTop + imgHeight,
          left: imgLeft + imgWidth
        });

        // --- 定位 toolbar ---

        // 计算初步结果
        let top = imgTop + imgHeight;
        let left = imgLeft;
        let marginLeft = 0;

        let txtTop = $currentTxt.position().top;
        let txtHeight = $currentTxt.outerHeight();
        if (top > (txtTop + txtHeight)) {
          // top 不得超出编辑范围
          top = txtTop + txtHeight;
        } else {
          // top 超出编辑范围，dragPoint就不显示了
          $dragPoint.show();
        }

        // 显示（方便计算 margin）
        $toolbar.show();

        // 计算 margin
        let width = $toolbar.outerWidth();
        marginLeft = imgWidth / 2 - width / 2;

        // 定位
        $toolbar.css({
          top: top + 5,
          left: left,
          'margin-left': marginLeft
        });
        // 如果定位太靠左了
        if (marginLeft < 0) {
          // 得到三角形的margin-left
          $toolbar.css('margin-left', '0');
          $triangle.hide();
        } else {
          $triangle.show();
        }

        // disable 菜单
        editor.disableMenusExcept();
      }

      // 隐藏 toolbar
      function hide() {
        if ($currentImg == null) {
          return;
        }
        $currentImg.removeClass('clicked');
        $currentImg = null;

        $toolbar.hide();
        $dragPoint.hide();

        // enable 菜单
        editor.enableMenusExcept();
      }

      // 判断img是否是一个表情
      function isEmotion(imgSrc) {
        let result = false;
        if (!editor.emotionUrls) {
          return result;
        }
        $.each(editor.emotionUrls, function (index, url) {
          let flag = false;
          if (imgSrc === url) {
            result = true;
            flag = true;
          }
          if (flag) {
            return false;  // break 循环
          }
        });
        return result;
      }

      // click img 事件
      $currentTxt.on('mousedown', 'img', function (e) {
        e.preventDefault();
      }).on('click', 'img', function (e) {
        let $img = $(e.currentTarget);
        let src = $img.attr('src');

        if (!src || isEmotion(src)) {
          // 是一个表情图标
          return;
        }

        // ---------- 不是表情图标 ----------

        // 渲染
        render();

        if ($currentImg && ($currentImg.get(0) === $img.get(0))) {
          setTimeout(hide, 100);
          return;
        }

        // 显示 toolbar
        $currentImg = $img;
        show();

        // 默认显示menuContainer，其他默认隐藏
        $menuContainer.show();
        $linkInputContainer.hide();

        // 阻止冒泡
        e.preventDefault();
        e.stopPropagation();

      }).on('click keydown scroll', function (e) {
        if (!isOnDrag) {
          setTimeout(hide, 100);
        }
      });

    });

  });
// 编辑区域 link toolbar
  _e(function (E, $) {
    E.plugin(function () {
      let editor = this;
      let lang = editor.config.lang;
      let $txt = editor.txt.$txt;

      // 当前命中的链接
      let $currentLink;

      let $toolbar = $('<div class="txt-toolbar"></div>');
      let $triangle = $('<div class="tip-triangle"></div>');
      let $triggerLink = $('<a href="#" target="_blank"><i class="wangeditor-menu-img-link"></i> ' + lang.openLink + '</a>');
      let isRendered;

      // 记录当前的显示/隐藏状态
      let isShow = false;

      let showTimeoutId, hideTimeoutId;
      let showTimeoutIdByToolbar, hideTimeoutIdByToolbar;

      // 渲染 dom
      function render() {
        if (isRendered) {
          return;
        }

        $toolbar.append($triangle)
          .append($triggerLink);

        editor.$editorContainer.append($toolbar);

        isRendered = true;
      }

      // 定位
      function setPosition() {
        if (!$currentLink) {
          return;
        }

        let position = $currentLink.position();
        let left = position.left;
        let top = position.top;
        let height = $currentLink.height();

        // 初步计算top值
        let topResult = top + height + 5;

        // 判断 toolbar 是否超过了编辑器区域的下边界
        let menuHeight = editor.menuContainer.height();
        let txtHeight = editor.txt.$txt.outerHeight();
        if (topResult > menuHeight + txtHeight) {
          topResult = menuHeight + txtHeight + 5;
        }

        // 最终设置
        $toolbar.css({
          top: topResult,
          left: left
        });
      }

      // 显示 toolbar
      function show() {
        if (isShow) {
          return;
        }

        if (!$currentLink) {
          return;
        }

        render();

        $toolbar.show();

        // 设置链接
        let href = $currentLink.attr('href');
        $triggerLink.attr('href', href);

        // 定位
        setPosition();

        isShow = true;
      }

      // 隐藏 toolbar
      function hide() {
        if (!isShow) {
          return;
        }

        if (!$currentLink) {
          return;
        }

        $toolbar.hide();
        isShow = false;
      }

      // $txt 绑定事件
      $txt.on('mouseenter', 'a', function (e) {
        // 延时 500ms 显示toolbar
        if (showTimeoutId) {
          clearTimeout(showTimeoutId);
        }
        showTimeoutId = setTimeout(function () {
          let a = e.currentTarget;
          let $a = $(a);
          $currentLink = $a;

          let $img = $a.children('img');
          if ($img.length) {
            // 该链接下包含一个图片

            // 图片点击时，隐藏toolbar
            $img.click(function (e) {
              hide();
            });

            if ($img.hasClass('clicked')) {
              // 图片还处于clicked状态，则不显示toolbar
              return;
            }
          }

          // 显示toolbar
          show();
        }, 500);
      }).on('mouseleave', 'a', function (e) {
        // 延时 500ms 隐藏toolbar
        if (hideTimeoutId) {
          clearTimeout(hideTimeoutId);
        }
        hideTimeoutId = setTimeout(hide, 500);
      }).on('click keydown scroll', function (e) {
        setTimeout(hide, 100);
      });
      // $toolbar 绑定事件
      $toolbar.on('mouseenter', function (e) {
        // 先中断掉 $txt.mouseleave 导致的隐藏
        if (hideTimeoutId) {
          clearTimeout(hideTimeoutId);
        }
      }).on('mouseleave', function (e) {
        // 延时 500ms 显示toolbar
        if (showTimeoutIdByToolbar) {
          clearTimeout(showTimeoutIdByToolbar);
        }
        showTimeoutIdByToolbar = setTimeout(hide, 500);
      });
    });
  });
// menu吸顶
  _e(function (E, $) {

    E.plugin(function () {
      let editor = this;
      let menuFixed = editor.config.menuFixed;
      if (menuFixed === false || typeof menuFixed !== 'number') {
        // 没有配置菜单吸顶
        return;
      }
      let bodyMarginTop = parseFloat(E.$body.css('margin-top'), 10);
      if (isNaN(bodyMarginTop)) {
        bodyMarginTop = 0;
      }

      let $editorContainer = editor.$editorContainer;
      let editorTop = $editorContainer.offset().top;
      let editorHeight = $editorContainer.outerHeight();

      let $menuContainer = editor.menuContainer.$menuContainer;
      let menuCssPosition = $menuContainer.css('position');
      let menuCssTop = $menuContainer.css('top');
      let menuTop = $menuContainer.offset().top;
      let menuHeight = $menuContainer.outerHeight();

      let $txt = editor.txt.$txt;

      E.$window.scroll(function () {
        //全屏模式不支持
        if (editor.isFullScreen) {
          return;
        }

        let sTop = E.$window.scrollTop();

        // 需要重新计算宽度，因为浏览器可能此时出现滚动条
        let menuWidth = $menuContainer.width();

        // 如果 menuTop === 0 说明此前编辑器一直隐藏，后来显示出来了，要重新计算相关数据
        if (menuTop === 0) {
          menuTop = $menuContainer.offset().top;
          editorTop = $editorContainer.offset().top;
          editorHeight = $editorContainer.outerHeight();
          menuHeight = $menuContainer.outerHeight();
        }

        if (sTop >= menuTop && sTop + menuFixed + menuHeight + 30 < editorTop + editorHeight) {
          // 吸顶
          $menuContainer.css({
            position: 'fixed',
            top: menuFixed
          });

          // 固定宽度
          $menuContainer.width(menuWidth);

          // 增加body margin-top
          E.$body.css({
            'margin-top': bodyMarginTop + menuHeight
          });

          // 记录
          if (!editor._isMenufixed) {
            editor._isMenufixed = true;
          }
        } else {
          // 取消吸顶
          $menuContainer.css({
            position: menuCssPosition,
            top: menuCssTop
          });

          // 取消宽度固定
          $menuContainer.css('width', '100%');

          // 还原 body margin-top
          E.$body.css({
            'margin-top': bodyMarginTop
          });

          // 撤销记录
          if (editor._isMenufixed) {
            editor._isMenufixed = false;
          }
        }
      });
    });

  });
// 缩进 菜单插件
  _e(function (E, $) {

    // 用 createMenu 方法创建菜单
    E.createMenu(function (check) {

      // 定义菜单id，不要和其他菜单id重复。编辑器自带的所有菜单id，可通过『参数配置-自定义菜单』一节查看
      let menuId = 'indent';

      // check将检查菜单配置（『参数配置-自定义菜单』一节描述）中是否该菜单id，如果没有，则忽略下面的代码。
      if (!check(menuId)) {
        return;
      }

      // this 指向 editor 对象自身
      let editor = this;

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,  // 编辑器对象
        id: menuId,  // 菜单id
        title: '缩进', // 菜单标题

        // 正常状态和选中装下的dom对象，样式需要自定义
        $domNormal: $('<a href="#" tabindex="-1"><i class="wangeditor-menu-img-indent-left"></i></a>'),
        $domSelected: $('<a href="#" tabindex="-1" class="selected"><i class="wangeditor-menu-img-indent-left"></i></a>')
      });

      // 菜单正常状态下，点击将触发该事件
      menu.clickEvent = function (e) {
        let elem = editor.getRangeElem();
        let p = editor.getSelfOrParentByName(elem, 'p');
        let $p;

        if (!p) {
          // 未找到 p 元素，则忽略
          return e.preventDefault();
        }
        $p = $(p);

        // 使用自定义命令
        function commandFn() {
          $p.css('text-indent', '2em');
        }

        editor.customCommand(e, commandFn);
      };

      // 菜单选中状态下，点击将触发该事件
      menu.clickEventSelected = function (e) {
        let elem = editor.getRangeElem();
        let p = editor.getSelfOrParentByName(elem, 'p');
        let $p;

        if (!p) {
          // 未找到 p 元素，则忽略
          return e.preventDefault();
        }
        $p = $(p);

        // 使用自定义命令
        function commandFn() {
          $p.css('text-indent', '0');
        }

        editor.customCommand(e, commandFn);
      };

      // 根据当前选区，自定义更新菜单的选中状态或者正常状态
      menu.updateSelectedEvent = function () {
        // 获取当前选区所在的父元素
        let elem = editor.getRangeElem();
        let p = editor.getSelfOrParentByName(elem, 'p');
        let $p;
        let indent;

        if (!p) {
          // 未找到 p 元素，则标记为未处于选中状态
          return false;
        }
        $p = $(p);
        indent = $p.css('text-indent');

        if (!indent || indent === '0px') {
          // 得到的p，text-indent 属性是 0，则标记为未处于选中状态
          return false;
        }

        // 找到 p 元素，并且 text-indent 不是 0，则标记为选中状态
        return true;
      };

      // 增加到editor对象中
      editor.menus[menuId] = menu;
    });

  });
// 行高 菜单插件
  _e(function (E, $) {

    // 用 createMenu 方法创建菜单
    E.createMenu(function (check) {

      // 定义菜单id，不要和其他菜单id重复。编辑器自带的所有菜单id，可通过『参数配置-自定义菜单』一节查看
      let menuId = 'lineheight';

      // check将检查菜单配置（『参数配置-自定义菜单』一节描述）中是否该菜单id，如果没有，则忽略下面的代码。
      if (!check(menuId)) {
        return;
      }

      // this 指向 editor 对象自身
      let editor = this;

      // 由于浏览器自身不支持 lineHeight 命令，因此要做一个hook
      editor.commandHooks.lineHeight = function (value) {
        let rangeElem = editor.getRangeElem();
        let targetElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5,pre');
        if (!targetElem) {
          return;
        }
        $(targetElem).css('line-height', value + '');
      };

      // 创建 menu 对象
      let menu = new E.Menu({
        editor: editor,  // 编辑器对象
        id: menuId,  // 菜单id
        title: '行高', // 菜单标题
        commandName: 'lineHeight', // 命令名称

        // 正常状态和选中装下的dom对象，样式需要自定义
        $domNormal: $('<a href="#" tabindex="-1"><i class="wangeditor-menu-img-arrows-v"></i></a>'),
        $domSelected: $('<a href="#" tabindex="-1" class="selected"><i class="wangeditor-menu-img-arrows-v"></i></a>')
      });

      // 数据源
      let data = {
        // 格式： 'value' : 'title'
        '1.0': '1.0倍',
        '1.5': '1.5倍',
        '1.8': '1.8倍',
        '2.0': '2.0倍',
        '2.5': '2.5倍',
        '3.0': '3.0倍'
      };

      // 为menu创建droplist对象
      let tpl = '<span style="line-height:{#commandValue}">{#title}</span>';
      menu.dropList = new E.DropList(editor, menu, {
        data: data,  // 传入数据源
        tpl: tpl  // 传入模板
      });

      // 增加到editor对象中
      editor.menus[menuId] = menu;

    });

  });
// 自定义上传
  _e(function (E, $) {

    E.plugin(function () {

      let editor = this;
      let customUpload = editor.config.customUpload;
      if (!customUpload) {
        return;
      } else if (editor.config.uploadImgUrl) {
        alert('自定义上传无效，详看浏览器日志console.log');
        E.error('已经配置了 uploadImgUrl ，就不能再配置 customUpload ，两者冲突。将导致自定义上传无效。');
        return;
      }

      let $uploadContent = editor.$uploadContent;
      if (!$uploadContent) {
        E.error('自定义上传，无法获取 editor.$uploadContent');
      }

      // UI
      let $uploadIcon = $('<div class="upload-icon-container"><i class="wangeditor-menu-img-upload"></i></div>');
      $uploadContent.append($uploadIcon);

      // 设置id，并暴露
      let btnId = 'upload' + E.random();
      let containerId = 'upload' + E.random();
      $uploadIcon.attr('id', btnId);
      $uploadContent.attr('id', containerId);

      editor.customUploadBtnId = btnId;
      editor.customUploadContainerId = containerId;
    });

  });
// 版权提示
  _e(function (E, $) {
    //E.info('本页面富文本编辑器由 WangEditor 提供 http://wangeditor.github.io/ ');
  });

  // 最终返回WangEditor构造函数
  return WangEditor;
});
